<?php
/* 模型
*
*【与JS同步修改】

*/

namespace hpnWse\nMvc {


use \hpnWse\stBoolUtil;
use \hpnWse\stNumUtil;
use \hpnWse\stStrUtil;
use \hpnWse\stAryUtil;
use \hpnWse\stObjUtil;
use \hpnWse\stFctnUtil;

use \hpnWse\nMvc\unMdl;

/// 是模型？
function fIsMdl($a_Any)
{
	return $a_Any instanceof tMdl;
}

/// 是资源？即非纯对象非数组非tMdl的对象
///【PHP: array不是对象，所以不用检查】
function fIsRsrc($a_Any)
{
	// && (! \hpnWse\fIsPureObjOrAry($a_Any)) 
	return is_object($a_Any) && (! fIsMdl($a_Any));
};

/// 存取嵌套属性，【警告】若中间属性不存在会抛出异常！
/// a_Tgt：Object$tMdl，目标，可以是tMdl的实例
/// a_Path：String，路径，形如"A.B"或"A[B]"，a_Tgt是tMdl时可用“^”回到父模型，如"A.^"、"A.^.^.C"
/// a_fSuplPv：Any f(a_Tgt, a_Obj, a_Pn)，补充属性值，当a_Obj没有a_Pn时进行回调，以返回值替代；
///		注意，如果提供了a_Pv，而存取的最后一个属性不存在，那么不会进行回调，而是执行a_Obj[a_Pn] = a_Pv;
/// a_Pv：属性值，如果提供则将写入至该属性，不提供表示读取
/// 返回：读取的属性值，或写入的a_Pv
function fAcsNestPpty($a_Tgt, $a_Path, $a_fSuplPv = null, $a_Pv = null)
{
	// 如果有效则率先去除所有空白并转交内部函数处理，否则返回目标对象
//	a_Path = a_Path && stStrUtil.cRmvAllWhtSpcs(a_Path); //【留着空白吧，以防键中有空白】
	$l_AgmAmt = func_num_args();
	return \hpnWse\fBool($a_Path)
		? unMdl\fAcsNestPpty($a_Tgt, $a_Path, $a_fSuplPv, $a_Pv, ($l_AgmAmt < 4)) 
		: $a_Tgt;
};

/// 分解路径，【flyweight】
///【返回】a_Rst：Object{ c_Mdl：String，模型路径； c_Fld：String，字段路径 }
function fDcmpsPath($a_Rst, $a_Path)
{
	$a_Rst = array('c_Mdl'=>'', 'c_Key'=>null);

	$a_Path = unMdl\fDotNestPn($a_Path);
	$l_LastDotIdx = stStrUtil::cRvsFind($a_Path, '.');
	if ($l_LastDotIdx < 0)
	{
		$a_Rst['c_Mdl'] = '';
		$a_Rst['c_Fld'] = $a_Path;
	}
	else
	{
		$a_Rst['c_Mdl'] = stStrUtil::cSub($a_Path, 0, $l_LastDotIdx);
		$a_Rst['c_Fld'] = stStrUtil::cSub($a_Path, $l_LastDotIdx + 1);
	}
	return $a_Rst;
};

/// 制作排序比较函数
/// a_tElmt：Wse::$i_，元素类名，可以是'Object'（默认），'tMdl'
/// a_Keys：String，键，支持多键排序，以逗号“,”分隔
/// a_Dirs：String，方向，与a_Keys匹配，"+"（默认）=升序，"-"=降序，以逗号“,”分隔
/// a_fCnvt：Number$String f(a_K, a_V)，转换函数，默认“return a_V”
/// a_RandKey：String，表示“随机”的键，默认"random"，因为a_tElmt可能不含“随机”字段，故需单独提供
function fMakeSortCmpr($a_tElmt, $a_Keys, $a_Dirs, $a_fCnvt = null, $a_RandKey = 'random')
{
	$l_ForObj = (! $a_tElmt) || (\Wse::$i_tObject === $a_tElmt);
	if (! $a_fCnvt) { $a_fCnvt = function ($a_K, $a_V) { return $a_V; }; }
	if (! $a_RandKey) { $a_RandKey = 'random'; }

	$l_fCmpr = null;
	if (stStrUtil::cFind($a_Keys, ',') < 0)
	{
		$l_fCmpr = function ($a_JM1, $a_JM2)
		{
			if ($a_RandKey == $a_Keys)
			{ return stNumUtil::cRandSign(); }

			$l_V1 = $a_fCnvt($a_Keys, $l_ForObj ? $a_JM1[$a_Keys] : $a_JM1->cReadFld($a_Keys));
			$l_V2 = $a_fCnvt($a_Keys, $l_ForObj ? $a_JM2[$a_Keys] : $a_JM2->cReadFld($a_Keys));
			$l_Rst = ($l_V1 < $l_V2) ? -1 : +1;
			return ('-' == $a_Dirs) ? -$l_Rst : $l_Rst;
		};
	}
	else
	{
		$a_Keys = explode(',', $a_Keys);
		$a_Dirs = explode(',', $a_Dirs);
		$l_fCmpr = function ($a_JM1, $a_JM2)
		{
			$l_Rst = -1;
			$l_Len = count($a_Keys);
			$i; $l_K; $l_V1; $l_V2;
			for ($i = 0; $i<$l_Len; ++$i)
			{
				$l_K = $a_Keys[$i];
				if ($a_RandKey == $l_K)
				{
					$l_Rst = stNumUtil::cRandSign();
					break;
				}

				$l_V1 = a_fCnvt($l_K, $l_ForObj ? $a_JM1[$l_K] : $a_JM1->cReadFld($l_K));
				$l_V2 = a_fCnvt($l_K, $l_ForObj ? $a_JM2[$l_K] : $a_JM2->cReadFld($l_K));
				if ($l_V1 != $l_V2)
				{
					$l_Rst = ($l_V1 < $l_V2) ? -1 : +1;
					if ('-' == $a_Dirs[$i])
					{ $l_Rst = -$l_Rst; }
					break;
				}
			}
			return $l_Rst;
		};
	}
	return $l_fCmpr;
};

// 模型标志
class tMdlFlag
{
	/// 接受错误值
	const i_Err = 0x01;
	
	/// 常量
	const i_Cst = 0x02;
	
	/// 隐藏
	const i_Hide = 0x04;
	
	/// 只读
	const i_Rdol = 0x08;
}

//【对应的标志索引，仅用于实现，不要使用！】
class utFi
{
	const i_Fi_Err = 0;
	const i_Fi_Cst = 1;
	const i_Fi_Hide = 2;
	const i_Fi_Rdol = 3;
}


/// 模型事件名
class tMdlEvtName
{
	/// 模型准备卸载（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	/// a_IsBbl：Boolean，是否为冒泡事件？
	const i_MdlPrprUlod = 'i_MdlPrprUlod';
	
	/// 模型加载完毕（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	const i_MdlLoadOver = 'i_MdlLoadOver';
	
	/// 模型准备重新加载（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	const i_MdlPrprRlod = 'i_MdlPrprRlod';
	
	/// 模型锁定改变（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	const i_MdlLockChgd = 'i_MdlLockChgd';
	
	/// 模型创建字段（冒泡），回调函数签名：void f(a_Mdl, a_Key, a_Val, a_ByUi, a_IsBbl)
	const i_MdlCrtFld = 'i_MdlCrtFld';
	
	/// 模型更新字段（冒泡），回调函数签名：void f(a_Mdl, a_Key, a_Val, a_OldVal, a_ByUi, a_IsBbl)
	/// 将在更新字段的i_EgnValUpd触发后触发
	const i_MdlUpdFld = 'i_MdlUpdFld';
	
	/// 模型删除字段（冒泡），回调函数签名：void f(a_Mdl, a_Key, a_Val, a_ByUi, a_IsBbl)
	const i_MdlDltFld = 'i_MdlDltFld';
	
	/// 模型排序字段（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	const i_MdlSortFlds = 'i_MdlSortFlds';
	
	/// 模型搜索字段（冒泡），回调函数签名：void f(a_Mdl, a_IsBbl)
	const i_MdlSrchFlds = 'i_MdlSrchFlds';

	/// 模型验证成功，回调函数签名：void f(a_Mdl, a_Null)
	const i_MdlVldtSucs = 'i_MdlVldtSucs';

	/// 模型验证失败，回调函数签名：void f(a_Mdl, a_Info)
	const i_MdlVldtFail = 'i_MdlVldtFail';

	/// 本征值隐藏变化，回调函数签名：void f(a_Mdl, a_Key, a_Val, a_Hide)
	const i_EgnValHideChgd = 'i_EgnValHideChgd';

	/// 本征值只读变化，回调函数签名：void f(a_Mdl, a_Key, a_Val, a_Rdol)
	const i_EgnValRdolChgd = 'i_EgnValRdolChgd';

	/// 本征值更新了，回调函数签名：void f(a_Mdl, a_Key, a_Val, a_OldVal, a_ByUi)
	/// a_ByUi：Object，这次更新是由哪个界面元素引起？【PHP：总是null】
	/// 将在所属模型的i_MdlUpdFld触发前触发
	const i_EgnValUpd = 'i_EgnValUpd';
	
	/// 本征值验证成功，回调函数签名：void f(a_Mdl, a_Key, a_Val, a_OldVal)
	const i_EgnValVldtSucs = 'i_EgnValVldtSucs';
	
	/// 本征值验证失败，回调函数签名：void f(a_Mdl, a_Key, a_Val, a_Info)
	/// a_Vldtr：atVldtr，未通过的验证器
	const i_EgnValVldtFail = 'i_EgnValVldtFail';
}

/// 模型
class tMdl
{
	//---- 字段

	public $e_Tpnm;
	public $e_Prn;
	public $e_TrgrQue;
	public $e_FldSet;
	public $e_Rkfk;
	public $e_Cmn;
	public $e_LockCnt;

	//---- 接口

	/// 构造
	/// a_Json：JSON，默认null表示空模型，如果有效就从其加载
	/// a_Dsrlz：Boolean，反序列化？
	public function __construct($a_Json = null, $a_Dsrlz = false)
	{
		$this->e_Tpnm = 'tMdl';	// 类型名
		$this->e_Prn = null;	// 父模型
		$this->e_TrgrQue = new unMdl\tTrgrQue();	// 触发队列
		$this->e_FldSet = null;	// 字段集合，可以是Object或Array
		$this->e_Rkfk = null;	// 远程键字段键，仅当字段集合是数组时才考虑
		$this->e_Cmn = new unMdl\tCmn(true);	// 字段公共
		$this->e_LockCnt = 0; // 锁定计数

		if ($a_Json)
		{ unMdl\fMapFromJson($this, $a_Json, $a_Dsrlz); }
	}

	///【说明】禁止克隆，因为涉及到过滤器、验证器等的克隆，请调用scCopyByFlds
	private function __clone()
	{
       throw new \Exception('tMdl禁止克隆！', -1);
    }

    /// 获取自身类型名
	public function cGetSelfTpnm()
	{
		return $this->e_Tpnm;
	}

	/// 存取父模型
	public function cAcsPrn()
	{
		return $this->e_Prn;
	}

	/// 存取根模型
	public function cAcsRoot()
	{
		$l_Rst = $this;
		while ($l_Rst->e_Prn)
		{ $l_Rst = $l_Rst->e_Prn; }
		return $l_Rst;
	}

	/// 位于根？即是否没有父模型？
	public function cAtRoot()
	{
		return (null === $this->e_Prn);
	}

	/// 位于叶？即是否没有子模型？
	public function cAtLeaf()
	{
		if ($this->cIsEmt())
		{ return true; }

		$l_Set = &$this->e_FldSet;
		$l_Key;
		foreach ($l_Set as $l_Key => $l_Vvv)
		{
			if (fIsMdl($l_Set[$l_Key]))
			{ return false; }
		}
		return true;
	}

	/// 是否锁定？【注意】锁定后不能进行CUD等修改模型的操作（忽略调用），但可以读取、验证
	public function cIsLock()
	{
		return ($this->e_LockCnt > 0);
	}

	/// 锁定整颗子树，【警告】必须与cUlok配对调用
	public function cLock()
	{
		unMdl\fLockUlok($this, true);
		return unMdl\fTrgrQue($this);
	}

	/// 解锁整颗子树，【警告】必须与cLock配对调用
	public function cUlok()
	{
		unMdl\fLockUlok($this, false);
		return unMdl\fTrgrQue($this);
	}

	/// 是空模型？【注意】如果字段集合是数组但不含元素，或是对象但不含属性，不算空模型
	public function cIsEmt()
	{
		return (null === $this->e_FldSet);
	}

	/// 字段集合是数组？【注意】空模型的字段集合是null
	public function cIsFldSetAry()
	{
		return (! $this->cIsEmt()) && \hpnWse\fIsAry($this->e_FldSet, true); // 快速检测
	}

	/// 字段集合是对象？【注意】空模型的字段集合是null
	public function cIsFldSetObj()
	{
		return (! $this->cIsEmt()) && (! $this->cIsFldSetAry());
	}

	/// 获取字段数量
	public function cGetFldAmt()
	{
		if ($this->cIsEmt())
		{ return 0; }

		return count($this->e_FldSet);
	}

	/// 有任何字段？
	public function cHasAnyFld()
	{
		return ($this->cGetFldAmt() > 0); // 不很高效，无妨
	}

	/// 获取自身在父模型中的键，若为根模型则返回null
	public function cGetSelfKey()
	{
		$l_Prn = $this->e_Prn;
		if (! $l_Prn)
		{ return null; }

		$l_PrnSet = &$l_Prn->e_FldSet; $l_Set = &$this->e_FldSet;
		$l_Key; $l_RmtFld;

		if ($l_Prn->cIsEmt()) // 如果父模型为空，不作处理，最后会引发异常
		{
		}
		else
		if ($l_Prn->cIsFldSetAry()) // 父模型的字段集合为Array
		{
			$l_Key = stAryUtil::cIdxOf($l_PrnSet, $this);
			if ($l_Key >= 0)
			{ return strval($l_Key); }
		}
		else // 为Object
		{
			foreach ($l_PrnSet as $l_Key => $l_Vvv)
			{
				if ($this === $l_PrnSet[$l_Key])
				{ return $l_Key; }
			}
		}
		throw new \Exception('父子模型的连接关系错误！', -1);
	}

	/// 获取自身在父模型中的远程键，若为根模型则返回null
	public function cGetSelfRmtKey()
	{
		$l_Prn = $this->e_Prn;
		return $l_Prn ? $l_Prn->cToRmtKey($this->cGetSelfKey()) : null;
	}

	/// 获取远程键字段键
	public function cGetRmtKeyFldKey()
	{
		return $this->e_Rkfk;
	}

	/// 设置远程键字段键，仅当字段集合为数组时有意义
	/// a_SubMdlFldKey：String，子模型字段键，子模型的这一字段值将作为键，而不是在数组中的索引
	public function cSetRmtKeyFldKey($a_SubMdlFldKey)
	{
		$this->e_Rkfk = \hpnWse\fBool($a_SubMdlFldKey) ? $a_SubMdlFldKey : null;
		return $this;
	}

	/// 获取自身的路径，若this是根则返回空串
	/// a_UseRmtKey：Boolean，使用远程键？默认false表示使用本地键拼入属性名
	public function cGetSelfPath($a_UseRmtKey = false)
	{
		if ($this->cAtRoot())
		{ return ''; }

		$l_Part = ($a_UseRmtKey && $this->e_Prn && \hpnWse\fBool($this->e_Prn->e_Rkfk))
					? $this->cGetSelfRmtKey() : $this->cGetSelfKey();
		if (! \hpnWse\fBool($l_Part)) { $l_Part = $this->cGetSelfKey(); }
		$l_PrnPath = $this->e_Prn->cGetSelfPath($a_UseRmtKey);
		return unMdl\fCmbnPath($this->e_Prn->cIsFldSetAry(), strval($l_PrnPath), strval($l_Part));
	}

	/// 获取全部字段的键
	/// a_CtanHide：Boolean，包含隐藏字段？默认false将跳过隐藏字段，若字段集为数组则总是true
	/// 返回：String[]，空模型返回空数组
	public function cGetAllFldKey($a_CtanHide = false)
	{
		return $this->cIsEmt() ? array() : array_keys($this->e_FldSet);

		if ($this->cIsEmt()) { return array(); }
		if ($a_CtanHide || $this->cIsFldSetAry()) { return array_keys($this->e_FldSet); }
		
		$l_Rst = array();
		$l_Set = $this->e_FldSet;
		foreach ($l_Set as $l_Key => $l_Fld)
		{
			if (stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide))
			{ continue; }
			
			$l_Rst[] = $l_Key;
		}
		return $l_Rst;
	}

	/// 获取字段的路径，若this是根则返回a_Key
	/// a_Key：String$Number，键，必须有效
	/// a_UseRmtKey：参见cGetSelfPath
	public function cGetFldPath($a_Key, $a_UseRmtKey = false)
	{
		$l_Part = $a_UseRmtKey ? $this->cToRmtKey($a_Key) : $a_Key;
		if (! \hpnWse\fBool($l_Part)) { $l_Part = $a_Key; }
		$l_SelfPath = $this->cGetSelfPath($a_UseRmtKey);
		return unMdl\fCmbnPath($this->cIsFldSetAry(), $l_SelfPath, strval($l_Part));
	}

	/// 获取被删除字段的路径，若this是根则返回a_Key
	/// a_Key：String$Number，键，必须有效
	/// a_Val：Null$原语值$tMdl，被删除的字段值，当是模型且a_UseRmtKey为true时，从中提取远程键
	/// a_UseRmtKey：参见cGetSelfPath
	public function cGetDltdFldPath($a_Key, $a_Val, $a_UseRmtKey = false)
	{
		$l_Part = $a_UseRmtKey ? \hpnWse\fV1OrV2($this->cRmtKeyFromSubMdl($a_Val), $a_Key) : $a_Key;
		if (! \hpnWse\fBool($l_Part)) { $l_Part = $a_Key; }
		$l_SelfPath = $this->cGetSelfPath($a_UseRmtKey);
		return unMdl\fCmbnPath($this->cIsFldSetAry(), $l_SelfPath, strval($l_Part));
	}

	/// 转成相对路径
	/// a_AbsPath：String，绝对路径，从根模型开始
	/// 返回：String，相对于本模型的路径，若开头恰是数组索引（如“[0].???”），将脱掉“[]”
	public function cToRelPath($a_AbsPath, $a_UseRmtKey = false)
	{
		$l_Rst = $a_AbsPath;
		$l_SelfPath = $this->cGetSelfPath($a_UseRmtKey);
		$l_SPL = stStrUtil::cGetLen($l_SelfPath);
		if ($l_SelfPath == stStrUtil::cSub($a_AbsPath, 0, $l_SPL))
		{ $l_Rst = stStrUtil::cSub($a_AbsPath, $l_SPL); }
		
		$l_Idx; $l_Num;
		if ('[' == $l_Rst[0]) // [
		{
			$l_Idx = stStrUtil::cFind($l_Rst, ']');
			$l_Num = stStrUtil::cSub($l_Rst, 1, $l_Idx);
			if ($l_Idx == stStrUtil::cGetLen($l_Rst) - 1)
			{ $l_Rst = $l_Num; }
			else
			{ $l_Rst = $l_Num . '.' . stStrUtil::cSub($l_Rst, $l_Idx + 1); }
		}
		return $l_Rst;
	}

	/// 本地键←远程键
	/// 返回：未指定远程键、空模型、没找到时为null，找到时为Number$String，
	///     若字段集合非数组，或a_RmtKey已是本地键，则原样返回
	public function cToLocKey($a_RmtKey)
	{
		if (\hpnWse\fIsUdfnOrNullOrEstr($a_RmtKey))
		{ return null; }

		if ((! $this->cIsFldSetAry()) || (0 != stStrUtil::cFind(strval($a_RmtKey), self::$sc_RmtKeySign)))
		{ return $this->cIsEmt() ? null : $a_RmtKey; }

		$a_RmtKey = stStrUtil::cSub($a_RmtKey, stStrUtil::cGetLen(self::$sc_RmtKeySign)); // 去掉符号

		$l_Ary = &$this->e_FldSet;
		$l_Key; $l_Fld; $l_Len = count($l_Ary);
		$l_RmtFldVal; $l_CRK; $l_RmtIdx;

		// 没有指定远程键，若a_RmtKey在索引有效范围内则原样返回（无论类型如何），否则返回null
		$l_RmtKey = $this->e_Rkfk;
		if (! $l_RmtKey)
		{ return null; }

		// 查找与指定的远程键相匹配的本地键，注意远程键可能有多个
		$l_IsSgl = (stStrUtil::cFind($l_RmtKey, self::$sc_RmtKeySprt) < 0);
		$l_RmtKeys = $l_IsSgl ? null : explode(self::$sc_RmtKeySprt, $l_RmtKey);
		for ($l_Key=0; $l_Key<$l_Len; ++$l_Key)
		{
			$l_Fld = $l_Ary[$l_Key];
		//	if (! nMvc\fIsMdl($l_Fld)) // 跳过非模型字段【没必要】
		//	{ continue; }

			// 子模型读取远程键字段，与传入的远程键比较
			if ($l_IsSgl) // 单个
			{
				$l_RmtFldVal = $l_Fld->cReadFld($l_RmtKey);
				if ($l_RmtFldVal == $a_RmtKey) // 使用“==”，允许隐式转型
				{ return strval($l_Key); }
			}
			else // 多个
			{
				$l_CRK = unMdl\fCmbnRmtKey($l_RmtKeys, $l_Fld);
				if ($l_CRK == $a_RmtKey)
				{ return strval($l_Key); }
			}
		}
		return null;
	}

	/// 远程键←本地键
	/// 返回：未指定远程键、空模型、没找到时为null，找到时为String，
	///     若字段集合非数组，或a_Key已是远程键，则原样返回
	public function cToRmtKey($a_Key)
	{
		if (\hpnWse\fIsUdfnOrNullOrEstr($a_Key))
		{ return null; }

		if ((! $this->cIsFldSetAry()) || (0 == stStrUtil::cFind(strval(a_Key), self::$sc_RmtKeySign)))
		{ return $this->cIsEmt() ? null : $a_Key; }

		$l_Ary = &$this->e_FldSet;
		$l_LocIdx = intval($a_Key);
		if (($l_LocIdx < 0) || (count($l_Ary) <= $l_LocIdx))
		{ return null; }

		// 没有指定远程键，原样返回（无论类型如何）
		$l_RmtKey = $this->e_Rkfk;
		if (! $l_RmtKey)
		{ return null; }

		// 返回由指定的本地键标识的子模型的远程键，对于非模型字段不区分两种键
		$l_Fld = $l_Ary[$l_LocIdx];
		return $this->cRmtKeyFromSubMdl($l_Fld);
	}

	/// 远程键←子模型
	/// 返回：a_SubMdl非模型时返回null，若未指定远程键，则返回null，否则返回String表示远程键
	public function cRmtKeyFromSubMdl($a_SubMdl)
	{
		if (! nMvc\fIsMdl($a_SubMdl))
		{ return null; }

		$l_RmtKey = $this->e_Rkfk;
		if (! $l_RmtKey)
		{ return null; }

		// 如果远程键只涉及一个字段
		$l_Rst = '';
		$l_IsSgl = (stStrUtil::cFind($l_RmtKey, self::$sc_RmtKeySprt) < 0);
		$l_RmtKeys = $l_IsSgl ? null : explode(self::$sc_RmtKeySprt, $l_RmtKey);
		if ($l_IsSgl)
		{
			$l_Rst = $a_SubMdl->cReadFld($l_RmtKey);
			return \hpnWse\fIsUdfn($l_Rst) ? null : (self::$sc_RmtKeySign . strval($l_Rst));
		}

		// 涉及多个，分别读取并装配起来
		$l_Rst = self::$sc_RmtKeySign . unMdl\fCmbnRmtKey($l_RmtKeys, $a_SubMdl);
		return $vl_Rst;
	}

	/// 从模型装载，转交cLoadFromJson
	public function cLoadFromMdl($a_Mdl, $a_Dsrlz = false)
	{
		$l_Json = $a_Mdl->cToJson(null, $a_Dsrlz);
		return $this->cLoadFromJson($l_Json, $a_Dsrlz);
	}

	/// 从JSON装载，若模型非空则先卸载
	/// a_Json：Object$Array，JSON
	/// a_Dsrlz：Boolean，反序列化？若为true则会读取选项、加载过滤器、验证器等，默认false
	public function cLoadFromJson($a_Json, $a_Dsrlz = false)
	{
		unMdl\fLoadFromJson($this, $a_Json, $a_Dsrlz);
		return unMdl\fTrgrQue($this);
	}

	/// 卸载
	///【说明】主要用于分页显示，与
	public function cUlod()
	{
		unMdl\fUlod($this, false);
		return unMdl\fTrgrQue($this);
	}

	/// 转成JSON，【注意，禁用字段不会出现在JSON中】
	/// a_Json：【警告，目前必须传null，该参数保留将来使用】
	/// a_Srlz：Boolean，序列化？若为true则会写入选项、保存验证器等，默认false
	/// 返回：a_Json，空模型返回空对象
	public function cToJson($a_Json = null, $a_Srlz = false)
	{
		return unMdl\fMapToJson($this, $a_Json, $a_Srlz);
	}

	/// 经由路径执行CRUD，【警告】若中间字段不存在则抛出异常！
	/// a_Path：详见nMvc.fAcsNestPpty，可以包含远程键（以sc_RmtKeySign开头）
	/// a_Val：undefined$null$原语值$Object，不传表示读取，传undefined表示删除，其余表示CU
	/// 返回：若读取则返回字段值（undefined表示字段不存在），若CUD则返回this
	public function cCrudViaPath($a_Path, $a_Val = null)
	{
		$l_AgmAmt = func_num_args();
		if ($l_AgmAmt < 2)
		{
			return fAcsNestPpty($this, $a_Path);
		}
		else
		{
			fAcsNestPpty($this, $a_Path, null, $a_Val);
			return $this;
		}
	}

	/// 经由路径尝试读取，【警告】若中间字段或目标字段不存在则返回undefined
	/// a_Path：详见nMvc.fAcsNestPpty，可以包含远程键（以sc_RmtKeySign开头）
	public function cTryReadViaPath($a_Path)
	{
		$l_TgtFld = \Wse::$i_Udfn;
		try { $l_TgtFld = $this->cCrudViaPath($a_Path); }
		catch (Exception $a_Exc) { }
		return $l_TgtFld;
	}

	/// 查找插入索引
	/// a_Val：同cCrtFld的a_InitVal
	/// a_fCmpr：int f(a_Ary[i], a_Val)，比较函数，返回正数的i作为返回值
	/// 返回：索引，可作为cCrtFld的第一个参数
	public function cFindIstIdx($a_Val, $a_fCmpr)
	{
		if ($this->cIsEmt())
		{ return 0; }

		if (! $this->cIsFldSetAry())
		{ \hpnWse\fTrhExc('cFindIstIdx只能用于空模型或字段集合为数组的模型！'); }

		$l_Ary = &$this->e_FldSet; $l_Len = count($l_Ary); $l_Idx;
		for ($l_Idx = 0; $l_Idx<$l_Len; ++$l_Idx)
		{
			if ($a_fCmpr($l_Ary[$l_Idx], $a_Val) > 0)
			{ break; }
		}
		return $l_Idx;
	}

	/// 创建字段，将触发i_MdlCrtFld事件
	/// a_Key：String$Number，键，必须有效，
	///		对于空模型，如果希望字段集合成为Array则传非负Number或"[$]"，否则成为Object，
	///		对于非空模型，若字段集合为Array则传插入索引或"[$]"，后者表示压入到最后，
	///		如果字段本身将作为模型，且其字段集合将成为Array，则可带有远程键，以分隔符分隔，如果"Books#id"
	/// a_InitVal：原语值$Array$Object，初始值，若为对象还可以表示配置对象：
	///	{
	///	Wse_Val：Null$原语值$Array$Object，值，该属性的存在表明这是一个配置对象，否则将被作为普通对象处理
	///	Wse_Tpnm：String，类型名∈{ "Boolean", "Number", "String", "Resource", "tMdl" }，
	///		Wse_Val为null时必须提供，
	///		非null时，若Wse_Val为String，而Wse_Tpnm为"Boolean"或"Number"，则进行类型转换
	/// Wse_Optns：Object，选项，默认为null：
	/// 	{
	/// 	c_Rqrd：Number，必填，0（默认）=允许为空（null或""），1=初值可空但更新不可空，2=不可空
	///		c_Dft：原语值$Null，默认null，可以是空串，【注意】仅用于本征值，模型的默认值总是null
	///		c_Flag：Number，标志，取自tFlag
	/// 	}
	/// Wse_Fltrs：atFltr[]，过滤器，默认为null，每个元素是Object：
	/// 	{
	///		c_Tpnm：String，类型名，表示在nFltr上注册的类型名，若未注册则被忽略
	///		...：特定于c_Tpnm表示的过滤器的参数
	///		}
	///	Wse_Vldtrs：atVldtr[]，验证器数组，默认为null，每个元素是Object：
	///		{
	///		c_Tpnm：String，类型名，表示在nVldtr上注册的类型名，若未注册则被忽略
	///		c_Info：String，验证器的信息，可以包含占位符（名称同其余表示参数的属性名），默认null
	///		...：特定于c_Tpnm表示的验证器的参数
	///		}
	///	}
	/// a_Dsrlz：参见cLoadFromJson的
	/// a_ByUi：Object，这次更新是由哪个界面元素引起？【PHP：总是为null】
	public function cCrtFld($a_Key, $a_InitVal, $a_Dsrlz = false, $a_ByUi = null)
	{
		unMdl\fCrtFld($this, $a_Key, $a_InitVal, $a_Dsrlz, $a_ByUi, false);
		return unMdl\fTrgrQue($this);
	}

	/// 读取字段
	/// 返回：Undefined$原语值$tMdl$Null，字段值，若为undefined则表示字段不存在
	public function cReadFld($a_Key)
	{
		// 若不存在返回undefined
		if (\hpnWse\fIsUdfnOrNullOrEstr($a_Key) || $this->cIsEmt())
		{ return \Wse::$i_Udfn; }

		// 如果是子模型则原样返回，否则返回本征值的值
		$l_Fld = unMdl\fAcsFld($this, $a_Key, false);
		return (\hpnWse\fIsUdfn($l_Fld) || fIsMdl($l_Fld)) ? $l_Fld : $l_Fld->e_Val;
	}

	/// 读取字段作为配置项
	/// 返回：当指定字段不存在或为null时返回a_Dft，
	/// 	若为tMdl则转成JSON（此时若a_Dft是Object则会以其为参照补充缺失属性）
	public function cReadFldAsCfg($a_Key, $a_Dft)
	{
		$l_Rst = $this->cReadFld($a_Key);
		if (\hpnWse\fIsUdfnOrNullOrEstr($l_Rst))
		{ return $a_Dft;}
		
		$l_Pn;
		if (fIsMdl($l_Rst))
		{
			$l_Rst = $l_Rst->cToJson();
			if (is_array($a_Dft))
			{
				foreach ($a_Dft as $l_Pn => $l_Pv)
				{
					if ((! isset($l_Rst[$l_Pn])) || \hpnWse\fIsNullOrEstr($l_Rst[$l_Pn]))
					{ $l_Rst[$l_Pn] = $a_Dft[$l_Pn]; }
				}
			}
		}
		return $l_Rst;
	}

	/// 读取最后一个字段，仅用于数组字段集
	/// 返回：非数组字段集或不存在时返回undefined
	public function cReadLastFld()
	{
		return $this->cIsFldSetAry() ? $this->e_FldSet[count($this->e_FldSet) - 1] : \Wse::$i_Udfn;
	}

	/// 读取多个字段，【注意】将跳过禁用字段
	///【忽略】a_Rst：Array，结果，若为null则自动新建，元素类型取决于a_ValOnly
	///     a_ValOnly=false时元素是Kvp：{ c_Key: String, c_Val: 原语值$tMdl$Null }
	///     a_ValOnly=true时元素是原语值$tMdl$Null
	/// a_ValOnly：Boolean，只要值？默认false
	/// a_fFltr：Boolean f(a_Mdl, a_Key, a_Val)$String[]，过滤器或键数组，返回true的字段将被选中，默认null表示全部选中
	/// a_fCmpr：比较函数，int f(a_Kvp1, a_Kvp2)，默认null表示不进行排序
	/// 返回：a_Rst
	public function cReadFlds($a_Rst, $a_ValOnly, $a_fFltr = null, $a_fCmpr = null)
	{
		//【PHP：总是新建一个数组并返回】
		$a_Rst = array();
		return unMdl\fReadFlds($this, $a_Rst, $a_ValOnly, $a_fFltr, $a_fCmpr);
	}

	/// 查找字段，【注意】将跳过禁用字段，仅适用于数组字段集！
	/// a_fCabk：Boolean，f(a_Mdl, a_Key, a_Val)，返回true时返回本地键
	/// a_Bgn：Number，起始索引，默认0
	public function cFindFld($a_fCabk, $a_Bgn = 0)
	{
		return unMdl\fFindFld($this, $a_fCabk, $a_Bgn);
	}

	/// 获取字段默认值
	public function cGetFldDft($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return $l_Fld->e_Cmn->e_Optns['c_Dft'];
	}

	/// 更新字段，将先后触发i_EgnValUpd、i_MdlUpdFld事件，【注意，被更新的字段不能是模型】
	/// a_Key：String，键，必须有效，且对应字段集合的类型（Array<-->索引名，Object<-->属性名）
	/// a_Val：原语值$Array$Object资源$Null，值
	/// a_ByUi：Boolean，这次操作是否由界面引起？默认false
	public function cUpdFld($a_Key, $a_Val, $a_ByUi = null)
	{
		$l_Chgd = unMdl\fUpdFld($this, $a_Key, $a_Val, $a_ByUi);
		unMdl\fTrgrQue($this);
		self::$sc_Chgd = $l_Chgd; // 返回前才设置
		return $this;
	}

	/// 更新字段成默认值
	public function cUpdFldsToDft($a_ByUi = null)
	{
		unMdl\fUpdFldsToDft($this, $a_ByUi);
		return unMdl\fTrgrQue($this);
	}

	/// 从JSON更新字段，忽略没有对应关系的字段
	///【注意，如果某个字段是子模型，当对应的a_Json属性值是纯对象时递归，其余忽略】
	public function cUpdFldsFromJson($a_Json, $a_ByUi = null)
	{
		$l_Chgd = unMdl\fUpdFldsFromJson($this, $a_Json, $a_ByUi);
		unMdl\fTrgrQue($this);
		self::$sc_Chgd = $l_Chgd; // 返回前才设置
		return $this;
	}

	/// 删除字段，将触发i_MdlDltFld事件
	/// a_Key：String，键，必须有效，且对应字段集合的类型（Array<-->索引名，Object<-->属性名）
	public function cDltFld($a_Key, $a_ByUi = null)
	{
		unMdl\fDltFld($this, $a_Key, $a_ByUi);
		return unMdl\fTrgrQue($this);
	}

	/// 删除多个字段，将触发i_MdlDltFld事件
	/// a_fIf：Boolean f(a_ThisMdl, a_Key, a_Val)，若返回true则删除，【警告】不要在回调期间修改模型！
	public function cDltFlds($a_fIf, $a_ByUi = null)
	{
		unMdl\fDltFlds($this, $a_fIf, $a_ByUi);
		return unMdl\fTrgrQue($this);
	}

	/// 清空字段集合，将触发i_MdlDltFld事件
	public function cClrFldSet($a_ByUi = null)
	{
		unMdl\fClrFldSet($this, $a_ByUi);
		return unMdl\fTrgrQue($this);
	}

	/// 排序字段，将触发i_MdlSortFlds事件，
	///【注意】仅当字段集合是数组时才有意义，且禁用字段总是排在后面
	/// a_fCmpr：比较函数，int f(a_V1, a_V2)，默认null表示不进行排序
	public function cSortFlds($a_fCmpr)
	{
		unMdl\fSortFlds($this, $a_fCmpr);
		return unMdl\fTrgrQue($this);
	}

	/// 排序子模型，内部转交cSortFlds
	/// 参数详见scMakeSortCmpr
	public function cSortSubMdls($a_Keys, $a_Dirs, $a_fCnvt = null, $a_RandKey = 'random')
	{
		return $this->cSortFlds(fMakeSortCmpr('tMdl', $a_Keys, $a_Dirs, $a_fCnvt, $a_RandKey));
	}

	/// 搜索字段，将触发i_MdlSrchFlds事件，并启用选中的字段，禁用未选中的字段，
	///【注意】仅当字段集合是数组时才有意义，排序只在选中的字段上进行
	/// a_fSrch：搜索函数，Boolean f(a_ThisMdl, a_Key, a_Val)，默认null表示选中
	/// a_fCmpr：比较函数，int f(a_V1, a_V2)，默认null表示不进行排序
	public function cSrchFlds($a_fSrch = null, $a_fCmpr = null)
	{
		unMdl\fSrchFlds($this, $a_fSrch, $a_fCmpr);
		return unMdl\fTrgrQue($this);
	}

	/// 搜索子模型，内部转交cSrchFlds
	/// a_fSrch：参见cSrchFlds
	/// a_SortKeys，a_SortDirs，a_fSortCnvt：参见cSortSubMdls
	public function cSrchSubMdls($a_fSrch, $a_SortKeys, $a_SortDirs, 
								$a_fSortCnvt = null, $a_RandKey = 'random')
	{
		return $this->cSrchFlds($a_fSrch, 
			fMakeSortCmpr('tMdl', $a_SortKeys, $a_SortDirs, $a_fSortCnvt, $a_RandKey));
	}

	// 【只适用于本质值】
	// /// 自身常量？
	// public function cIsSelfCst()
	// {
	// 	return stNumUtil::cGetBit($this->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Cst);
	// }

	/// 字段是否常量？
	public function cIsFldCst($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Cst);
	}

	/// 字段是否接受错误值
	public function cIsFldAcpErr()
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		$l_Flag = $l_Fld->e_Cmn->e_Optns['c_Flag'];
		return (! stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Cst)) && stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Err);
	}
	
	/// 字段接受错误值，只适用于非常量本征值，若字段是模型则递归应用
	public function cFldAcpErr($a_Key, $a_YesNo)
	{
		unMdl\fChgEgnValFlag($this, $a_Key, \hpnWse\fBool($a_YesNo), true, utFi::i_Fi_Err, null);
		return unMdl\fTrgrQue($this);
	}
	
	/// 所有字段接受错误值，只适用于非常量本征值，若字段是模型则递归应用
	public function cAllFldsAcpErr($a_YesNo)
	{
		unMdl\fChgAllEgnValFlag($this, \hpnWse\fBool($a_YesNo), true, utFi::i_Fi_Err, null);
		return unMdl\fTrgrQue($this);
	}

	/// 字段隐藏？只适用于本征值
	public function cIsFldHide($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide);
	}
	
	/// 字段隐藏，只适用于本征值，若字段是模型则递归应用
	public function cFldHide($a_Key, $a_YesNo)
	{
		unMdl\fChgEgnValFlag($this, $a_Key, \hpnWse\fBool($a_YesNo), false, utFi::i_Fi_Hide, 'i_EgnValHideChgd');
		return unMdl\fTrgrQue($this);
	}
	
	/// 所有字段隐藏，只适用于本征值，若字段是模型则递归应用
	public function cAllFldsHide($a_YesNo)
	{
		unMdl\fChgAllEgnValFlag($this, \hpnWse\fBool($a_YesNo), false, utFi::i_Fi_Hide, 'i_EgnValHideChgd');
		return unMdl\fTrgrQue($this);
	}

	/// 字段只读？只适用于本征值
	public function cIsFldRdol($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		$l_Flag = $l_Fld->e_Cmn->e_Optns['c_Flag'];
		return stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Cst) || stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Rdol);
	}
	
	/// 字段只读，只适用于非常量本征值，若字段是模型则递归应用
	public function cFldRdol($a_Key, $a_YesNo)
	{
		unKnl\fChgEgnValFlag($this, $a_Key, \hpnWse\fBool($a_YesNo), true, utFi::i_Fi_Rdol, 'i_EgnValRdolChgd');
		return unMdl\fTrgrQue($this);
	}
	
	/// 所有字段只读
	public function cAllFldsRdol($a_YesNo)
	{
		unKnl\fChgAllEgnValFlag($this, \hpnWse\fBool($a_YesNo), true, utFi::i_Fi_Rdol, 'i_EgnValRdolChgd');
		return unMdl\fTrgrQue($this);
	}

	//【待定】
	// /// 修正远程键字段，要求字段集合是数组，且已设置远程键字段键
	// /// a_Map：Object，键是当前远程键字段值，值是修正值
	// public function cFixRmtKeyFld($a_Map)
	// {
	// 	unMdl\fFixRmtKeyFld($this, $a_Map);
	// 	return unMdl\fTrgrQue($this);
	// }

	/// 验证字段
	/// 返回：Boolean，成败
	public function cVldtFld($a_Key)
	{
		$l_Rst = unMdl\fVldtFld($this, $a_Key, $a_ByUi);
		unMdl\fTrgrQue($this);
		return $l_Rst;
	}

	/// 验证
	/// a_ErrInfo：Object，错误信息，返回false时才会填写
	/// {
	///	c_Smry：String[]，总结
	/// c_Flds：Object，键是出错的字段，值是信息
	/// }
	/// a_MdlOnly：Boolean，只验证模型？默认false
	/// 返回：Boolean，成败
	public function cVldt(&$a_ErrInfo, $a_MdlOnly = false)
	{
		stObjUtil::cAcsObjPpty($a_ErrInfo, "c_Smry", null);
		stObjUtil::cAcsObjPpty($a_ErrInfo, "c_Flds", null);
		$l_Rst = unMdl\fVldtMdl($this, $a_ErrInfo, $a_MdlOnly);
		unMdl\fTrgrQue($this);
		return $l_Rst;
	}

	/// 获取字段类型名
	/// 返回："Boolean"$"Number"$"String"$"Resource"$"tMdl"，不存在时返回undefined
	public function cGetFldTpnm($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, false);
		return $l_Fld ? $l_Fld->e_Tpnm : \Wse::$i_Udfn;
	}

	/// 有字段？
	public function cHasFld($a_Key)
	{
		$l_Key = unMdl\fCnvtKey($this, strval($a_Key));
		return $this->e_FldSet ? array_key_exists($l_Key, $this->e_FldSet) : false;
	}

	/// 字段是本征值？
	public function cIsFldEgnVal($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, false);
		return $l_Fld ? (! fIsMdl($l_Fld)) : false;
	}

	/// 字段是模型？
	public function cIsFldMdl($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, false);
		return $l_Fld ? (fIsMdl($l_Fld)) : false;
	}

	/// 添加模型事件处理器
	/// a_EvtName：tEvtName，必须有效
	/// a_fHdl：void f(a_Mdl, a_Key, a_Val, ...)，后面参数取决于a_EvtName
	public function cAddMdlEvtHdlr($a_EvtName, $a_fHdl)
	{
		return unMdl\fAddRmvMdlEvt($this, true, $a_EvtName, $a_fHdl);
	}

	/// 移除模型事件处理器
	/// 参数同cAddMdlEvtHdlr
	public function cRmvMdlEvtHdlr($a_EvtName, $a_fHdl)
	{
		return unMdl\fAddRmvMdlEvt($this, false, $a_EvtName, $a_fHdl);
	}

	/// 添加字段事件处理器
	/// a_Key：String，键，对于本征值事件必须有效，模型事件忽略
	/// a_EvtName：tEvtName，必须有效
	/// a_fHdl：void f(a_Mdl, a_Key, a_Val, ...)，后面参数取决于a_EvtName
	public function cAddFldEvtHdlr($a_Key, $a_EvtName, $a_fHdl)
	{
		return unMdl\fAddRmvFldEvt($this, true, $a_Key, $a_EvtName, $a_fHdl);
	}

	/// 移除字段事件处理器
	/// 参数同cAddFldEvtHdlr
	public function cRmvFldEvtHdlr($a_Key, $a_EvtName, $a_fHdl)
	{
		return unMdl\fAddRmvFldEvt($this, false, $a_Key, $a_EvtName, $a_fHdl);
	}

	/// 清空模型事件处理器
	public function cClrMdlEvtHdlr()
	{
		return unMdl\fClrMdlEvtHdlr($this);
	}

	/// 清空字段事件处理器
	public function cClrFldEvtHdlr($a_Key)
	{
		return unMdl\fClrFldEvtHdlr($this, $a_Key);
	}

	/// 存取自身验证器数组，【警告】不要修改返回的数组！
	public function cAcsSelfVldtrAry()
	{
		return $this->e_Cmn->e_Vldtrs;
	}

	/// 根据类型名存取自身验证器
	public function cAcsSelfVldtrByTpnm($a_Tpnm)
	{
		return unMdl\fAcsFvByTpnm($this->e_Cmn->e_Vldtrs, $a_Tpnm);
	}

	/// 清空自身验证器
	public function cClrSelfVldtr()
	{
		stAryUtil::cZero($this->e_Cmn->e_Vldtrs);
		return $this;
	}

	/// 添加自身验证器
	/// a___：atVldtr，验证器实例，vcRun只有第一个参数有效，后两个参数为null、undefined
	public function cAddSelfVldtr($a___ = null)
	{
		$l_Vldtrs = &$this->e_Cmn->e_Vldtrs;
		$l_Agms = func_get_args(); $l_AgmAmt = func_num_args(); $i;
		for ($i = 0; $i<$l_AgmAmt; ++$i)
		{ $l_Vldtrs[] = ($l_Agms[$i]); }
		return $this;
	}

	/// 存取本征值过滤器数组
	/// a_Key：String，键
	public function cAcsEgnValFltrAry($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return $l_Fld->e_Cmn->e_Fltrs;
	}

	/// 根据类型名存取本征值过滤器
	public function cAcsEgnValFltrByTpnm($a_Key, $a_Tpnm)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return unMdl\fAcsFvByTpnm($l_Fld->e_Cmn->e_Fltrs, $a_Tpnm);
	}

	/// 清空本征值过滤器
	/// a_Key：String，键
	public function cClrEgnValFltr($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		stAryUtil::cZero($l_Fld->e_Cmn->e_Fltrs);
		return $this;
	}

	/// 添加本征值过滤器
	/// a_Key：String，键
	/// a___：atVldtr，过滤器实例
	public function cAddEgnValFltr($a_Key, $a___ = null)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		$l_Fltrs = &$l_Fld->e_Cmn->e_Fltrs;
		$l_Agms = func_get_args(); $l_AgmAmt = func_num_args(); $i;
		for ($i = 1; $i<$l_AgmAmt; ++$i)
		{ $l_Fltrs[] = ($l_Agms[$i]); }
		return $this;
	}

	/// 存取本征值验证器数组
	/// a_Key：String，键
	public function cAcsEgnValVldtrAry($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return $l_Fld->e_Cmn->e_Vldtrs;
	}

	/// 根据类型名存取本征值验证器
	public function cAcsEgnValVldtrByTpnm($a_Key, $a_Tpnm)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		return unMdl\fAcsFvByTpnm($l_Fld->e_Cmn->e_Vldtrs, $a_Tpnm);
	}

	/// 清空本征值验证器
	/// a_Key：String，键
	public function cClrEgnValVldtr($a_Key)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		stAryUtil::cZero($l_Fld->e_Cmn->e_Vldtrs);
		return $this;
	}

	/// 添加本征值验证器
	/// a_Key：String，键
	/// a___：atVldtr，验证器实例
	public function cAddEgnValVldtr($a_Key, $a___ = null)
	{
		$l_Fld = unMdl\fAcsFld($this, $a_Key, true);
		$l_Vldtrs = &$l_Fld->e_Cmn->e_Vldtrs;
		$l_Agms = func_get_args(); $l_AgmAmt = func_num_args(); $i;
		for ($i = 1; $i<$l_AgmAmt; ++$i)
		{ $l_Vldtrs[] = ($l_Agms[$i]); }
		return $this;
	}

	//---- 静态

	/// 远程键记号
	public static $sc_RmtKeySign = '#';

	/// 远程键分隔符
	public static $sc_RmtKeySprt = ',';

	/// 发生变化了？用于cUpdFld()和cUpdFldsFromJson
	public static $sc_Chgd = false;

	/// 提取远程键（即移除记号）
	public static function scExtrRmtKey($a_RmtKey)
	{
		$l_Idx = \hpnWse\fBool($a_RmtKey) ? stStrUtil::cFind($a_RmtKey, self::$sc_RmtKeySign) : -1;
		return (0 == $l_Idx) ? stStrUtil::cSub($a_RmtKey, 1) : $a_RmtKey;
	}

	/// 推导类型名
	/// a_Val：Any，若为undefined或null则返回a_Tpnm，否则据此进行推导，
	///		如推得"String"，且a_Tpnm为"Boolean"或"Number"，且a_NoCast为false，则返回a_Tpnm（类型转换），否则返回推导的类型
	/// a_Tpnm："Boolean"$"Number"$"String"$"Resource"$"tMdl"$null，用于类型转换，默认null
	/// a_NoCast：Boolean，不要类型转换？默认false
	/// 返回：Boolean$Number$String$tMdl$null，undefined表示推导失败
	public static function scIfrTpnm($a_Val, $a_Tpnm, $a_NoCast = false)
	{
		return unMdl\fIfrTpnm($a_Val, $a_Tpnm, $a_NoCast);
	}
	
	/// 根据字段拷贝
	/// a_Orig：tMdl，模型原本，必须有效
	/// a_Srlz：Boolean，序列化？若为true则会写入选项、保存验证器等，默认false
	public static function scCopyByFlds($a_Orig, $a_Srlz = false)
	{
		return unMdl\fCopy($a_Orig, $a_Srlz);
	}
	
	//【不是很有用，先去掉】
	// /// 根据字段相等？
	// /// a_L，a_R：tMdl，左右模型，必须有效
	// public static function scEqByFlds($a_L, $a_R)
	// {
	// 	return unMdl\fFldsEq($a_L, $a_R);
	// }
	
	// /// 根据字段同一类型？
	// /// a_L，a_R：tMdl，左右模型，必须有效
	// public static function scSampTypeByFlds($a_L, $a_R)
	// {
	// 	return unMdl\fFldsSampType($a_L, $a_R);
	// }

	/// 是模型事件？
	public static function scIsMdlEvt($a_EvtName)
	{
		return ! self::scIsEgnValEvt($a_EvtName);
	}

	/// 是本征值事件？
	public static function scIsEgnValEvt($a_EvtName)
	{
		return (0 === stStrUtil::cFind($a_EvtName, 'i_EgnVal'));
	}

	/// 组装子模型数组
	/// a_HostMdl：tMdl，宿主模型，a_Jsons装入这里，装入前先清空
	/// a_VldtMdl：tMdl，验证模型，用于验证a_Data的每一项
	/// a_Jsons：JSON[]，数据
	public static function scPpltSubMdls($a_HostMdl, $a_VldtMdl, $a_Jsons)
	{
		$a_HostMdl->cClrFldSet(); // 先清空
		$l_Len = count($a_Jsons);
		for ($i=0; $i<$l_Len; ++$i)
		{
			$a_VldtMdl->cUpdFldsToDft();
			$a_VldtMdl->cUpdFldsFromJson($a_Jsons[$i]);
			$l_Copy = self::scCopyByFlds($a_VldtMdl, true);
			$a_HostMdl->cCrtFld('[$]', $l_Copy);
		}
		$a_VldtMdl->cUpdFldsToDft(); // 应该复位
	}
}

} // namespace hpnWse\nMvc

////////////////////////////////////【文件空间】////////////////////////////////////

namespace hpnWse\nMvc\unMdl {

use \hpnWse\stBoolUtil;
use \hpnWse\stNumUtil;
use \hpnWse\stStrUtil;
use \hpnWse\stAryUtil;
use \hpnWse\stObjUtil;
use \hpnWse\stFctnUtil;

use \hpnWse\nMvc;
use \hpnWse\nMvc\tMdl;
use \hpnWse\nMvc\tMdlFlag;
use \hpnWse\nMvc\utFi;
use \hpnWse\nMvc\tMdlEvtName;

// 静态变量
class stVars
{
	public static $s_CnvtKey;

	public static $s_IfrdCastTpnm;

	public static $s_Key, $s_RmtKey;

	public static $s_InitVal, $s_Tpnm, $s_Optns, $s_Fltrs, $s_Vldtrs, $s_Rkfk;
}

//-------- 存取嵌套属性

/// 点化嵌套属性名，即把"A[B]"化成"A.B"
function fDotNestPn($a_NestPn)
{
	$i_Rgx = '/\[(.+?)\]/';
	return (stStrUtil::cFind($a_NestPn, '[') >= 0) 
			? preg_replace($i_Rgx, '.$1', $a_NestPn) : $a_NestPn;
}

function fFchPv($a_Tgt, $a_Obj, $a_Pn, $a_fSuplPv, $a_Exc)
{
	$l_IsMdl = nMvc\fIsMdl($a_Obj);
	if (('^' == $a_Pn) && $l_IsMdl)
	{
		return $a_Obj->cAcsPrn();
	}

	$l_Pv = $l_IsMdl ? $a_Obj->cReadFld($a_Pn) : $a_Obj[$a_Pn];

	// 若为undefined则进行回调
	if (\hpnWse\fIsUdfn($l_Pv) && $a_fSuplPv)
	{ $l_Pv = $a_fSuplPv($a_Tgt, $a_Obj, $a_Pn); }

	if ($a_Exc && \hpnWse\fIsUdfn($l_Pv)) // 若还是undefined，抛出异常
	{ throw new \Exception(('中间' . ($l_IsMdl ? '字段“' : '属性“') . $a_Pn . '”不存在！'), -1); }

	return $l_Pv;
}

function fAsnPv($a_Obj, $a_Pn, $a_Pv)
{
	$l_IsMdl = nMvc\fIsMdl($a_Obj);
	$l_FldVal;
	if ($l_IsMdl) // 若是模型则CUD相应字段
	{
		$l_FldVal = $a_Obj->cReadFld($a_Pn); // 读取字段值
		if (\hpnWse\fIsUdfn($l_FldVal))	// 字段尚不存在
		{
			if (! \hpnWse\fIsUdfn($a_Pv)) // 若提供了非undefined值
			{
				// 创建字段，若a_Pv为Object则进行反序列化
				$a_Obj->cCrtFld($a_Pn, $a_Pv, true);
			}
			// 否则忽略
		}
		else // 字段已存在
		{
			if (\hpnWse\fIsUdfn($a_Pv)) // 若提供了undefined值
			{
				// 删除字段
				$a_Obj->cDltFld($a_Pn);
			}
			else
			{
				// 更新字段
				$a_Obj->cUpdFld($a_Pn, $a_Pv);
			}
		}

		return $a_Obj->cReadFld($a_Pn); // 读取新值并返回
	}
	else // 非模型直接赋值即可
	{
		return ($a_Obj[$a_Pn] = $a_Pv);
	}
}

function fAcsNestPpty($a_Tgt, $a_Path, $a_fSuplPv, $a_Pv, $a_NoPv)
{
	if (! $a_Path)
	{ return $a_Tgt; }

	$a_Path = fDotNestPn($a_Path);

	$l_Obj = $a_Tgt;
	if (stStrUtil::cFind($a_Path, '.') < 0)
	{
		return $a_NoPv 
			? fFchPv($a_Tgt, $a_Tgt, $a_Path, $a_fSuplPv, false) 
			: fAsnPv($a_Tgt, $a_Path, $a_Pv);
	}

	$l_PnAry = explode('.', $a_Path);
	$l_Len = count($l_PnAry);
	for ($i = 0; $i<$l_Len && $l_Obj; ++$i)
	{
		// 如果没有a_Pv或这不是最后一次迭代，表示读取；否则表示写入
		$l_Pn = $l_PnAry[$i];
		$l_InMid = ($i < $l_Len - 1);
		$l_Obj = ($a_NoPv || $l_InMid) 
			? fFchPv($a_Tgt, $l_Obj, $l_Pn, $a_fSuplPv, $l_InMid) 
			: fAsnPv($l_Obj, $l_Pn, $a_Pv);
	}
	return $l_Obj;
}

/// 公共（内部类型）
class tCmn
{
	public $e_Optns;
	public $e_Fltrs;
	public $e_Vldtrs;
	public $e_On;

	public function __construct($a_IsMdl)
	{
		$this->e_Optns = fNewOptns();     // 选项
		$this->e_Fltrs = array();             // 过滤器
		$this->e_Vldtrs = array();	          // 验证器数组
		$this->eInitOn($a_IsMdl);          // 事件处理器
	}

	public function eInitOn($a_IsMdl)
	{
		$this->e_On = $a_IsMdl
				? array( 'i_MdlPrprUlod'=> null, 'i_MdlLoadOver'=> null, 'i_MdlPrprRlod'=> null, 
					'i_MdlLockChgd'=> null,
					'i_MdlCrtFld'=> null, 'i_MdlUpdFld'=> null, 'i_MdlDltFld'=> null, 
					'i_MdlSortFlds'=> null, 'i_MdlSrchFlds'=> null,
					'i_MdlVldtSucs'=> null, 'i_MdlVldtFail'=> null )
				: array( 'i_EgnValHideChgd'=> null, 'i_EgnValRdolChgd'=> null,
					'i_EgnValUpd'=> null, 'i_EgnValVldtSucs'=> null, 'i_EgnValVldtFail'=> null );
	}
}

/// 本征值（内部类型）
class tEgnVal
{
	public $e_Cmn;
	public $e_Val;
	public $e_Tpnm;

	public function __construct($a_Val, $a_Tpnm)
	{
		$this->eAsnValAndType($a_Val, $a_Tpnm, false);
		$this->e_Cmn = new tCmn(false);
	}

	public function eAsnValAndType($a_Val, $a_Tpnm, $a_UpdFld)
	{
		stVars::$s_IfrdCastTpnm = null; // 先复位
		if ((! $a_UpdFld) && (! \hpnWse\fIsUdfnOrNull($a_Val)) && $a_Tpnm) // 创建时，如果两者都提供了，按需自动转型
		{
			$a_Val = fCastEgnVal($a_Val, $a_Tpnm); // 将修改s_IfrdCastType
		}

		$this->e_Val = $a_Val;

		if ((! $a_UpdFld)) // 创建时，还要记录类型
		{
			$this->e_Tpnm = (null === $a_Val) ? $a_Tpnm 
				: \hpnWse\fV1OrV2(stVars::$s_IfrdCastTpnm, fIfrPrimType($a_Val));
		}
	}

	public function eFltr($a_Mdl, $a_Key, $a_Val)
	{
		$l_Fltrs = &$this->e_Cmn->e_Fltrs;
		$l_Len = count($l_Fltrs); $i;
		for ($i=0; $i<$l_Len; ++$i)
		{
			if (! $l_Fltrs[$i]->c_Dsab)
			{ $a_Val = $l_Fltrs[$i]->vcRun($a_Mdl, $a_Key, $a_Val); }
		}
		return $a_Val;
	}
	
	public function eVldt($a_IcldAsyn, $a_Mdl, $a_Key, $a_Val)
	{
		// 这是非穿越型，返回失败的验证器，【PHP：没有异步验证器】
		$l_ErrAry = array();
		$l_Vldtrs = &$this->e_Cmn->e_Vldtrs;
		return fVldtFor($l_Vldtrs, count($l_Vldtrs), false, $l_ErrAry, $a_Mdl, $a_Key, $a_Val);
	}
}

/// 触发队列
class tTrgrQue
{
	public $c_Que;
	public $c_Trvsn;

	public function __construct()
	{
		$this->c_Que = array();		// 队列
		$this->c_Trvsn = false;	// 正在遍历队列？
	}
}

//-------- 基础操作

// 分解键和远程键
function fDcmpsKeys($a_Key)
{
	stVars::$s_Key = $a_Key;//strval($a_Key); // 现在一定是字符串
	stVars::$s_RmtKey = null;
	$l_RmtKeySignIdx = stStrUtil::cFind(stVars::$s_Key, nMvc\tMdl::$sc_RmtKeySign);
	if (($l_RmtKeySignIdx >= 0))
	{
		stVars::$s_Key = stStrUtil::cSub($a_Key, 0, $l_RmtKeySignIdx);
		stVars::$s_RmtKey = stStrUtil::cSub($a_Key, $l_RmtKeySignIdx + 1);
	}
}

// 分解初值
function fDcmpsInitVal($a_InitVal, $a_Dsrlz)
{
	stVars::$s_InitVal = $a_InitVal;
	stVars::$s_Tpnm = null;
	stVars::$s_Optns = null;
	stVars::$s_Fltrs = null;
	stVars::$s_Vldtrs = null;
	stVars::$s_Rkfk = null;

	if (\hpnWse\fIsPureObjOrAry($a_InitVal)) // 纯Object或Array
	{
		if (stObjUtil::cHasPpty($a_InitVal, 'Wse_Val')) // 字段配置
		{
			stVars::$s_InitVal = $a_InitVal['Wse_Val'];
			stVars::$s_Tpnm = fTypeFromTpnm(stObjUtil::cFchPpty($a_InitVal, 'Wse_Tpnm')); // 无效也没关系，后面会推导

			if ($a_Dsrlz) // 如果反序列化
			{
				if (stObjUtil::cHasPpty($a_InitVal, 'Wse_Optns')) { stVars::$s_Optns = fLoadOptns($a_InitVal['Wse_Optns']); }
				stVars::$s_Fltrs = stObjUtil::cFchPpty($a_InitVal, 'Wse_Fltrs');
				stVars::$s_Vldtrs = stObjUtil::cFchPpty($a_InitVal, 'Wse_Vldtrs');
				stVars::$s_Rkfk = stObjUtil::cFchPpty($a_InitVal, 'Wse_Rkfk');
			}
		}
	//	else // 子模型
	}
	else
	if (nMvc\fIsRsrc($a_InitVal)) // 资源
	{
		stVars::$s_Tpnm = 'Resource';
	}
	// else // 其余后面会推导
}

// 组合远程键
function fCmbnRmtKey($a_RmtKeys, $a_SubMdl)
{
	$l_Rst = '';
	$l_Parts = $a_RmtKeys; $l_PartsLen = count($l_Parts); $p; $l_RmtFldVal;
	for ($p = 0; $p<$l_PartsLen; ++$p)
	{
		$l_RmtFldVal = $a_SubMdl->cReadFld($l_Parts[$p]);
		if (\hpnWse\fIsUdfn($l_RmtFldVal)) // 有任何一个不存在，就返回null
		{ return null; }

		$l_Rst .= strval($l_RmtFldVal);
		if ($p < $l_PartsLen - 1)
		{ $l_Rst .= tMdl::$sc_RmtKeySprt; }
	}
	return $l_Rst;
}

// 组合Path
function fCmbnPath($a_IsAry, $a_Path, $a_Part)
{
	if (! \hpnWse\fBool($a_Part)) { return $a_Path; }
	return \hpnWse\fBool($a_Path) 
			? ($a_IsAry ? ($a_Path . '[' . $a_Part . ']') : ($a_Path . '.' . $a_Part)) 
			: $a_Part;
}

// 标志相关
function fAcsFld($a_This, $a_Key, $a_ChkKey)
{
	stVars::$s_CnvtKey = fCnvtKey($a_This, strval($a_Key));
	if ($a_ChkKey)
	{ fChkKey($a_This, stVars::$s_CnvtKey); }

	return stObjUtil::cReadPpty($a_This->e_FldSet, stVars::$s_CnvtKey);
}

function fFchFldVal($a_Fld)
{
	return nMvc\fIsMdl($a_Fld) ? $a_Fld : $a_Fld->e_Val;
}

function fIfrPrimType($a_Val)
{
	if (\hpnWse\fIsBool($a_Val))	{ return 'Boolean'; }
	if (\hpnWse\fIsNum($a_Val))	{ return 'Number'; }
	if (\hpnWse\fIsStr($a_Val))	{ return 'String'; }
	if (nMvc\fIsRsrc($a_Val))	{ return 'Resource'; }

	return \Wse::$i_Udfn;
}

function fIfrTpnm($a_Val, $a_Tpnm, $a_NoCast = false)
{
	// 如果是undefined或null
	if (\hpnWse\fIsUdfnOrNull($a_Val))
	{ return $a_Tpnm || null; }

	// 如果是模型
	//【警告】本函数会被内部多次用于验证，不要随意地把Object映射成tMdl，是否映射由调用者控制
	if (nMvc\fIsMdl($a_Val))
	{ return 'tMdl'; }

	// 本征值推导类型，若不匹配，尝试转换
	$l_IfrdTpnm = fIfrPrimType($a_Val);
	if ((! $a_NoCast) && ($l_IfrdTpnm !== $a_Tpnm))
	{
		if (((('Boolean' === $a_Tpnm) || ('Number' === $a_Tpnm)) && ('String' === $l_IfrdTpnm)) ||
			(('String' === $a_Tpnm) && (('Boolean' === $l_IfrdTpnm) || ('Number' === $l_IfrdTpnm))))
		{ $l_IfrdTpnm = $a_Tpnm; }
	}
	return $l_IfrdTpnm;
}

function fCastEgnVal($a_Val, $a_Tpnm)
{
	stVars::$s_IfrdCastTpnm = fIfrPrimType($a_Val); // 可能是undefined
	$l_CastVal;
	if (\hpnWse\fBool(stVars::$s_IfrdCastTpnm) && (stVars::$s_IfrdCastTpnm !== $a_Tpnm))
	{
		if ('String' === stVars::$s_IfrdCastTpnm)
		{
			if ('Boolean' === $a_Tpnm)
			{
				$a_Val = ('true' == mb_strtolower($a_Val));
				stVars::$s_IfrdCastTpnm = $a_Tpnm;
			}
			else
			if ('Number' === $a_Tpnm)
			{
				$l_CastVal = doubleval($a_Val);
				if (! is_nan($l_CastVal)) // 若是NaN，保持输入值，否则转型【PHP：似乎不会产生NaN】
				{
					$a_Val = $l_CastVal;
					stVars::$s_IfrdCastTpnm = $a_Tpnm;
				}
			}
			else
			if ('Resource' === $a_Tpnm)
			{
				// 字符串可能是个URI，指向某个资源，算作资源
				stVars::$s_IfrdCastTpnm = $a_Tpnm;
			}
		}
		else
		if ('String' === $a_Tpnm)
		{
			$a_Val = strval($a_Val);
			stVars::$s_IfrdCastTpnm = $a_Tpnm;
		}
		else
		if ('Resource' === $a_Tpnm) // 任何值都可以作为资源
		{
			stVars::$s_IfrdCastTpnm = $a_Tpnm;
		}
	}
	return $a_Val;
}

//-------- 与JSON的双向映射

function fLoadFromJson($a_This, $a_Json, $a_Dsrlz)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 先卸载，这是为重加载
	fUlod($a_This, true);

	// 从JSON映射
	fMapFromJson($a_This, $a_Json, $a_Dsrlz);

	// 触发事件
	fTrgrUpToRoot('i_MdlLoadOver', array($a_This, false));
	return $a_This;
}

function fUlod($a_This, $a_ForRlod)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 空模型无需卸载
	if ($a_This->cIsEmt())
	{ return $a_This; }

	// 触发事件
	if ($a_ForRlod)
	{ fTrgrUpToRoot('i_MdlPrprRlod', array($a_This, false)); }
	else
	{ fTrgrUpToRoot('i_MdlPrprUlod', array($a_This, false)); }

	// 最后清空
	$a_This->e_FldSet = null;
	return $a_This;
}

function fMapFromJson($a_This, $a_Json, $a_Dsrlz)
{
	if (! \hpnWse\fIsPureObjOrAry($a_Json))
	{ throw new \Exception('a_Json要么是Array，要么是纯Object！', -1); }
	
	// 如果有“Wse_Val”，从其映射所有字段，否则从a_Json
	fMapFromJson_Flds($a_This, (isset($a_Json['Wse_Val']) ? $a_Json['Wse_Val'] : $a_Json), $a_Dsrlz);
	
	// 需要反序列化
	if ($a_Dsrlz && isset($a_Json['Wse_Val']))
	{
		fMapFromJson_Cmn($a_This->e_Cmn, 
			stObjUtil::cFchPpty($a_Json, 'Wse_Optns'), 
			stObjUtil::cFchPpty($a_Json, 'Wse_Fltrs'), 
			stObjUtil::cFchPpty($a_Json, 'Wse_Vldtrs'));
		if (stObjUtil::cHasPpty($a_Json, 'Wse_Rkfk')) { $a_This->cSetRmtKeyFldKey($a_Json['Wse_Rkfk']); }
	}
}

function fMapFromJson_Flds($a_This, $a_Json, $a_Dsrlz)
{
	$l_Set = $a_Json;
	$l_IsAry = \hpnWse\fIsAry($l_Set, true); // 快速判断
	$l_Key;
	foreach ($l_Set as $l_Key => $l_Vvv)
	{
		// 内部调用，不要触发事件
		fCrtFld($a_This, ($l_IsAry ? '[$]' : $l_Key), $a_Json[$l_Key], $a_Dsrlz, null, true); 
	}

	// 如果为空，说明是空对象或空数组，相应创建字段集
	if ($a_This->cIsEmt())
	{
		$a_This->e_FldSet = array();//\hpnWse\fIsPureObj($a_Json) ? array() : array(); //【PHP：都是数组】
	}	
	return $a_This;
}

function fMapFromJson_Cmn(&$a_Cmn, $a_Optns, $a_Fltrs, $a_Vldtrs)
{
	// 加载选项
	if ($a_Optns)
	{ $a_Cmn->e_Optns = fLoadOptns($a_Optns); }
	
	// 反序列化过滤器和验证器
	if (($a_Fltrs || $a_Vldtrs))
	{ nMvc\stFvMgr::eDsrlz($a_Cmn, $a_Fltrs, $a_Vldtrs); }
}

function fMapToJson($a_This, $a_Json, $a_Srlz)
{
	if ($a_This->cIsEmt()) // 空模型映射成空对象，除非有远程键，此时映射成空数组
	{ return array(); }// (\hpnWse\fBool($a_This->e_Rkfk) ? array() : array()); }
	
	// 映射所有字段
	$l_FldsJson = fMapToJson_Flds($a_This, $a_Srlz);
	
	// 如果不要求序列化，或不需要（对模型来说，远程键也需要序列化），返回字段映射结果即可
	if ((! $a_Srlz) || ((! $a_This->e_Rkfk) && (! fNeedSrlz($a_This->e_Cmn))))
	{ return $l_FldsJson; }
	
	// 使用“Wse_Val”保存字段结果
	$l_IsDrvdMdl = ('\\hpnWse\\nMvc\\tMdl' !== stObjUtil::cGetFullTpnm($a_This));
	$l_Rst = array(
		"Wse_Val" => $l_FldsJson,
		"Wse_Tpnm" => $l_IsDrvdMdl ? stObjUtil::cGetFullTpnm($a_This) : 'tMdl'
	);
	fMapToJson_Cmn($l_Rst, $a_This->e_Cmn);
	if ($a_This->e_Rkfk) { $l_Rst['Wse_Rkfk'] = $a_This->e_Rkfk; }
	return $l_Rst;
}

function fMapToJson_Flds($a_This, $a_Srlz)
{
	$l_Set = &$a_This->e_FldSet;
	$l_Rst = ($a_This->cIsFldSetAry() ? stAryUtil::cNew(count($l_Set)) : array());
	$l_Key; $l_Fld;
	foreach ($l_Set as $l_Key => $l_Vvv)
	{
		$l_Fld = $l_Set[$l_Key];
		if (stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide)) // 跳过隐藏
		{ continue; }

		$l_Rst[$l_Key] = nMvc\fIsMdl($l_Fld) ? fMapToJson($l_Fld, null, $a_Srlz) : fMapToJson_EgnVal($l_Fld, $a_Srlz);
	}
	return $l_Rst;
}

function fMapToJson_EgnVal($a_Fld, $a_Srlz)
{
	$l_Rst;
	if ($a_Srlz && fNeedSrlz($a_Fld->e_Cmn)) // 需用Wse_Val保存
	{
		$l_Rst = array( 'Wse_Val' => $a_Fld->e_Val, 'Wse_Tpnm' => $a_Fld->e_Tpnm );
		fMapToJson_Cmn($l_Rst, $a_Fld->e_Cmn);
		return $l_Rst;
	}
	else
	{
		return $a_Fld->e_Val;
	}
}

function fMapToJson_Cmn(&$a_Rst, $a_Cmn)
{
	$l_HasOptns = fHasNonDftOptn($a_Cmn->e_Optns);
	$l_HasFltrs = count($a_Cmn->e_Fltrs) > 0;
	$l_HasVldtrs = count($a_Cmn->e_Vldtrs) > 0;
	
	// 保存选项
	if ($l_HasOptns)
	{
		$a_Rst['Wse_Optns'] = array();
		fSaveOptns($a_Rst['Wse_Optns'], $a_Cmn->e_Optns);
	}
	
	// 序列化过滤器和验证器
	if (($l_HasFltrs || $l_HasVldtrs))
	{ nMvc\stFvMgr::eSrlz($a_Rst, $a_Cmn->e_Fltrs, $a_Cmn->e_Vldtrs); }
}

function fNeedSrlz($a_Cmn)
{
	$l_HasOptns = fHasNonDftOptn($a_Cmn->e_Optns);
	$l_HasFltrs = count($a_Cmn->e_Fltrs) > 0;
	$l_HasVldtrs = count($a_Cmn->e_Vldtrs) > 0;
	return $l_HasOptns || $l_HasFltrs || $l_HasVldtrs;
}

function fTypeFromTpnm($a_Tpnm)
{
	return \hpnWse\fV1OrV2($a_Tpnm, null);
	// return (('Boolean' == $a_Tpnm) || ('Number' == $a_Tpnm) || ('String' == $a_Tpnm) || 
	// 		('Resource' == $a_Tpnm) || ('tMdl' == $a_Tpnm))
	// 		? $a_Tpnm : null;
}

function fNewOptns()
{
	return array(
		'c_Rqrd' => 0,
		'c_Dft' => null,
		'c_Flag' => 0
	);
}

function fLoadOptns($a_Optns)
{
	$l_Src = $a_Optns;
	$l_Dst = array(
		'c_Rqrd' => stObjUtil::cFchPpty($l_Src, 'c_Rqrd', 0),
		'c_Dft' => stObjUtil::cFchPpty($l_Src, 'c_Dft', null),
		'c_Flag' => stObjUtil::cFchPpty($l_Src, 'c_Flag', 0)
	);
	return $l_Dst;
}

function fHasNonDftOptn($a_Optns)
{
	// 默认值：	0					null/""				0         
	return (\hpnWse\fBool($a_Optns['c_Rqrd']) || \hpnWse\fBool($a_Optns['c_Dft']) || \hpnWse\fBool($a_Optns['c_Flag']));
}

function fSaveOptns(&$a_Dst, $a_Optns)
{
	$l_Src = $a_Optns;
	$a_Dst['c_Rqrd'] = $l_Src['c_Rqrd'];
	$a_Dst['c_Dft'] = $l_Src['c_Dft'];
	$a_Dst['c_Flag'] = $l_Src['c_Flag'];
}

function fTryKey($a_This, $a_Key, $a_Rvs = false)
{
	if (\hpnWse\fIsUdfnOrNullOrEstr($a_Key))
	{ return 'a_Key无效！'; }

	if ($a_Rvs)
	{
		if ($a_This->e_FldSet && array_key_exists($a_Key, $a_This->e_FldSet))
		{ return '指定模型已存在字段“' . $a_Key . '”！'; }
	}
	else
	{
		if ($a_This->cIsEmt() || (! array_key_exists($a_Key, $a_This->e_FldSet)))
		{ return '指定模型没有字段“' . $a_Key . '”！'; }
	}
	return null;
}

function fChkKey($a_This, $a_Key, $a_Rvs = false) //【警告】a_Key必须是String
{
	$l_Err = fTryKey($a_This, $a_Key, $a_Rvs);
	if ($l_Err)
	{ throw new \Exception($l_Err, -1); }
}

function fCnvtKey($a_This, $a_Key) //【警告】a_Key必须是String
{
	$l_Idx = \hpnWse\fBool($a_Key) ? stStrUtil::cFind($a_Key, tMdl::$sc_RmtKeySign) : -1;
	return (0 == $l_Idx) ? $a_This->cToLocKey($a_Key) : \hpnWse\fV1OrV2($a_Key, '');
}

//-------- CRUD

function fCrtFld($a_This, $a_Key, $a_InitVal, $a_Dsrlz, $a_ByUi, $a_NoTrgr)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 检查a_Key
	if (\hpnWse\fIsUdfnOrNull($a_Key))
	{ throw new \Exception('a_Key不能为undefined、null！', -1); }

	// 判断是否为特殊键，或者索引，适当转换键
	$l_KeyIsIdx = false; $l_Key; $l_RmtKey = null;
	if ('[$]' == $a_Key)
	{
		$l_KeyIsIdx = true; // 转成索引
		$l_Key = $a_This->cGetFldAmt(); // 压入位置
	}
	else
	if (\hpnWse\fIsNum($a_Key))
	{
		$l_KeyIsIdx = true;
		$l_Key = min($a_Key, $a_This->cGetFldAmt()); // 最大为压入位置
	}

	// 检查l_Key的有效性
	if ($l_KeyIsIdx) // 索引
	{
		if ($a_This->cIsFldSetObj())
		{ throw new \Exception('字段集为对象时a_Key不能是索引或“[$]”！', -1); }

		if ($l_Key < 0)
		{ throw new \Exception('a_Key不能为负数！', -1); }
	}
	else // 字符串
	{
		fDcmpsKeys($a_Key);
		$l_Key = stVars::$s_Key;
		$l_RmtKey = stVars::$s_RmtKey;
		fChkKey($a_This, $l_Key, true); // 确保同名字段尚不存在
	}

	// 调整a_InitVal
	if (\hpnWse\fIsUdfn($a_InitVal)) // 不能为undefined
	{ throw new \Exception('初值不能为undefined！', -1); }

	fDcmpsInitVal($a_InitVal, $a_Dsrlz);	// 分解初值
	$l_InitVal = stVars::$s_InitVal; $l_Tpnm = stVars::$s_Tpnm;
	$l_Optns = stVars::$s_Optns; $l_Fltrs = stVars::$s_Fltrs; $l_Vldtrs = stVars::$s_Vldtrs;
	if (stVars::$s_Rkfk) { $l_RmtKey = stVars::$s_Rkfk; } // 覆盖键上的

	if ((null === $l_InitVal) && (! fTypeFromTpnm($l_Tpnm))) // 检查能否进行类型推导
	{ throw new \Exception('初值为null时必须指定有效的类型名！', -1); }

	$l_InitValIsMdl = nMvc\fIsMdl($l_InitVal);
	if ($l_InitValIsMdl && (! $l_InitVal->cAtRoot())) // 若是子模型，确保位于根
	{ throw new \Exception('初值若是子模型则必须位于根！', -1); }

	$l_InitValType; $l_DftType;	// 检查非null的默认值，null总是有效的默认值，undefined作为null处理
	if ($l_Optns && (! \hpnWse\fIsUdfnOrNull(stObjUtil::cFchPpty($l_Optns, 'c_Dft'))))
	{
		if ($l_InitValIsMdl)
		{ throw new \Exception('模型的默认值只能是null！', -1); }

		$l_InitValType = fIfrTpnm($l_InitVal, $l_Tpnm);
		$l_DftType = fIfrTpnm($l_Optns['c_Dft'], $l_Tpnm);
		if ($l_InitValType !== $l_DftType)
		{ throw new \Exception('默认值类型与字段类型不匹配！', -1); }
	}

	if ($l_Optns && (2 == stObjUtil::cFchPpty($l_Optns, 'c_Rqrd')) && // 检查初值必填
		\hpnWse\fIsNullOrEstr($l_InitVal) && \hpnWse\fIsUdfnOrNullOrEstr($l_Optns['c_Dft']))
	{ throw new \Exception(('必须为必填字段“' . $a_Key . '”提供非null非空串的初值或默认值！'), -1); }

	// 如果初值是纯Object或Array，则生成子模型并映射；在映射期间，如果有异常，就此退出
	if (\hpnWse\fIsPureObjOrAry($l_InitVal))
	{
		if ($l_Tpnm && ('tMdl' !== $l_Tpnm)) // 若l_Tpnm无效则默认"tMdl"
		{
			if (! class_exists($l_Tpnm))
			{
			//	console.warn("tMdl的派生类“" + l_Tpnm + "”不存在，替换为tMdl！");
				$l_Tpnm = '\hpnWse\nMvc\tMdl';
			}
		}
		else //【PHP：下面new的时候必须提供完全限定名】
	//	if ((! $l_Tpnm) || ('tMdl' === $l_Tpnm))
		{ $l_Tpnm = '\hpnWse\nMvc\tMdl'; }

		$l_InitVal = new $l_Tpnm($l_InitVal, $a_Dsrlz); // 提供两个参数构造，派生类必须接受第三个参数无效！
		$l_Tpnm = 'tMdl'; // 派生类的类型名也应该是"tMdl"
	}

	// 通过检查，内部创建或直接添加……
	fInrCrtFld($a_This, $l_KeyIsIdx, $l_Key, $l_RmtKey, $l_InitVal, $l_Tpnm, 
		$l_Optns, $l_Fltrs, $l_Vldtrs, $a_Dsrlz, $a_ByUi, $a_NoTrgr);
	return $a_This;
}

function fInrCrtFld($a_This, $a_KeyIsIdx, $a_Key, $a_RmtKey, $a_InitVal, $a_Tpnm, 
					$a_Optns, $a_Fltrs, $a_Vldtrs, $a_Dsrlz, $a_ByUi, $a_NoTrgr)
{
	// 字段要么是子模型（tMdl），要么是本征值（tEgnVal）
	$l_Fld; $l_IsEgnVal = false;
	if ((null === $a_InitVal)) // 如果是null
	{
		if ('tMdl' === $a_Tpnm) // 生成子模型，保持空
		{
			$l_Fld = new tMdl(); // 不要传入l_This，稍后再设置
		}
		else // 本征值
		{
			$l_Fld = new tEgnVal($a_InitVal, $a_Tpnm);
			$l_IsEgnVal = true;
		}
	}
	else
	if (nMvc\fIsMdl($a_InitVal)) // tMdl
	{
		$l_Fld = $a_InitVal;			// 子模型
	}
	else
	if ((is_object($a_InitVal) || \hpnWse\fIsPureObjOrAry($a_InitVal)) && 
		(! nMvc\fIsRsrc($a_InitVal))) // 非资源Object（包括Array）
	//【JS】if (nWse.fIsObj(a_InitVal) && (! nMvc.fIsRsrc(a_InitVal))) // 非资源Object（包括Array）
	{
		throw new \Exception('不可能执行至此！', -1);
	}
	else // 原语值
	{
		$l_Fld = new tEgnVal($a_InitVal, $a_Tpnm);	// 本征值
		$l_IsEgnVal = true;
	}

	fMapFromJson_Cmn($l_Fld->e_Cmn, $a_Optns, $a_Fltrs, $a_Vldtrs);
	$l_Optns = &$l_Fld->e_Cmn->e_Optns; //【PHP：取引用！】
	if ($l_IsEgnVal) // 如果是本征值，调整默认值和初值
	{
		if ($a_Optns &&
			(! \hpnWse\fIsUdfnOrNull(stObjUtil::cFchPpty($a_Optns, 'c_Dft')))) // 非undefined非null的默认值
		{ $l_Optns['c_Dft'] = $a_Optns['c_Dft']; }
		else
		if (('String' === $l_Fld->e_Tpnm)) // 默认值是undefined或null，且类型是String，改成空串
		{ $l_Optns['c_Dft'] = ''; }

		// 如果初值为null或空串，使用默认值
		if (\hpnWse\fIsNullOrEstr($l_Fld->e_Val))
		{ $l_Fld->e_Val = $l_Optns['c_Dft']; }
	}
	else // 子模型
	{
		if ($a_RmtKey)					// 如果有，设置远程键
		{ $l_Fld->cSetRmtKeyFldKey($a_RmtKey); }
	}

	//if ($l_Fld->e_Cmn->e_Fltrs) //【不用过滤初值了，要求创建时指定正确的初值！】
	//{ $l_Fld->e_Val = $l_Fld->eFltr($a_This, $a_Key, $l_Fld->e_Val); }

	// 添加字段
	fAddFld($a_This, $a_KeyIsIdx, $a_Key, $l_Fld, $a_ByUi, $a_NoTrgr);

	// 返回字段表示创建成功
	return $l_Fld;
}

function fAddFld($a_This, $a_KeyIsIdx, $a_Key, $a_Fld, $a_ByUi, $a_NoTrgr)
{
	$l_Key = $a_Key;

	// 新字段添加至字段集合
	if ($a_This->cIsEmt()) // 如果自身是空模型
	{
		// 当a_Key是索引时选Array，否则选Object
		$a_This->e_FldSet = array();//(! $a_KeyIsIdx) ? array() : array(); //【PHP：都是数组】
	}
	// 这里不要加else!
	if ($a_KeyIsIdx && $a_This->cIsFldSetAry()) // 如果是Array【PHP：加上第一个条件，因为“[]”认为是数组！】
	{
		stAryUtil::cIst($a_This->e_FldSet, $l_Key, $a_Fld); // 插入到恰当位置
	}
	else // Object而非Array
	{
		$a_This->e_FldSet[$l_Key] = $a_Fld;		// 作为属性赋值
	}

	if (nMvc\fIsMdl($a_Fld)) // 如果是子模型
	{
		$a_Fld->e_Prn = $a_This;			// 连接父子
	}

	// 触发事件
	$l_FldVal;
	if (! $a_NoTrgr)
	{
		$l_FldVal = fFchFldVal($a_Fld);
		fTrgrUpToRoot('i_MdlCrtFld', array($a_This, $l_Key, $l_FldVal, $a_ByUi, false));
	}
}

function fReadFlds($a_This, &$a_Rst, $a_ValOnly, $a_fFltr, $a_fCmpr)
{
	$a_Rst = $a_Rst ? $a_Rst : array();

	if ($a_This->cIsEmt())
	{ return $a_Rst; }

	$l_Set = &$a_This->e_FldSet;
	$l_Key; $l_Fld; $l_FldVal;
	if (is_array($a_fFltr))
	{
		// stAryUtil::cFor($a_fFltr, 
		// 	function ($a_Keys, $a_Idx, $a_Key) use($a_This, &$a_Rst)
		// 	{ $a_Rst[] = $a_This->cReadFld($a_Key); });
		for ($i=0; $i<count($a_fFltr); ++$i)
		{
			$l_Key = $a_fFltr[$i];
			$l_FldVal = $a_This->cReadFld($l_Key);
			$a_Rst[] = ($a_ValOnly ? $l_FldVal : array('c_Key'=>$l_Key, 'c_Val'=>$l_FldVal));
		}
	}
	else
	{
		foreach ($l_Set as $l_Key => $l_Vvv)
		{
			$l_Fld = $l_Set[$l_Key];
			if (stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide)) // 跳过隐藏
			{ continue; }

			if ((! $a_fFltr) || $a_fFltr($a_This, $l_Key, $l_Fld))
			{
				$l_FldVal = nMvc\fIsMdl($l_Fld) ? $l_Fld : $l_Fld->e_Val;
				$a_Rst[] = ($a_ValOnly ? $l_FldVal : array('c_Key'=>$l_Key, 'c_Val'=>$l_FldVal));
			}
		}
	}

	if ($a_fCmpr) // 如果需要排序
	{
		usort($a_Rst, function ($a_Kvp1, $a_Kvp2) use($a_fCmpr)
		{
			return $a_fCmpr($a_Kvp1, $a_Kvp2);
		});
	}
	return $a_Rst;
}

function fFindFld($a_This, $a_fCabk, $a_Bgn)
{
	$l_Set = &$a_This->e_FldSet;
	$l_IsAry = $a_This->cIsFldSetAry();
	if ((! $l_IsAry) || (0 == count($l_Set)))
	{ return -1; }
	
	if ($a_Bgn < 0)
	{ $a_Bgn = 0; }
	
	$l_IsMdl = nMvc\fIsMdl($l_Set[0]); // 假定同质
	$l_Key; $l_Fld; $l_FldVal;
	for ($l_Key=$a_Bgn; $l_Key<count($l_Set); ++$l_Key)
	{
		$l_Fld = $l_Set[$l_Key];
		if (\hpnWse\stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide)) // 跳过隐藏
		{ continue; }
		
		$l_FldVal = $l_IsMdl ? $l_Fld : $l_Fld->e_Val;
		if (a_fCabk($a_This, $l_Key, $l_FldVal))
		{ return $l_Key; }
	}
	return -1;
}

function fUpdFld($a_This, $a_Key, $a_Val, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return false; }

	// 检查参数
	$l_Key = $a_Key; //fCnvtKey(a_This, String(a_Key)); // 不用转换，因为不能是模型
	fChkKey($a_This, $l_Key);	// 确保同名字段已经存在

	if (\hpnWse\fIsUdfn($a_Val)) // 不能为undefined
	{ throw new \Exception('a_Val不能为undefined！', -1); }

	// 不能更新子模型
	$l_Fld = $a_This->e_FldSet[$l_Key];
	if (nMvc\fIsMdl($l_Fld))
	{ throw new \Exception('被更新的字段不能是模型！', -1); }

	// 是本征值……
	// 如果是常量或隐藏或只读，忽略
	$l_Optns = &$l_Fld->e_Cmn->e_Optns;
	$l_Flag = $l_Optns['c_Flag'];
	if (stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Cst) || 
		stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Hide) ||
		stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Rdol))
	{ return false; }

	// 先转换
	// 如果为null或空串，就取默认值
	// 如果类型匹配且不同于默认值就执行过滤，完后若没有变化则立即返回false，这一步是为了优化，因为fInrUpdFld有很多工作要做
	$a_Val = fCastEgnVal($a_Val, $l_Fld->e_Tpnm); // s_IfrdCastTpnm记录了a_Val转换后的类型名
	if (\hpnWse\fIsNullOrEstr($a_Val))
	{ $a_Val = $l_Optns['c_Dft']; }
	else
	if ((stVars::$s_IfrdCastTpnm === $l_Fld->e_Tpnm) && ($a_Val !== $l_Optns['c_Dft']))
	{ $a_Val = $l_Fld->eFltr($a_This, $l_Key, $a_Val); }

	if ($l_Fld->e_Val === $a_Val)
	{ return false; }

	// 更新，如果发生错误将触发验证失败事件且返回null，而不会抛出异常
	return fInrUpdFld($a_This, $l_Key, $a_Val, stVars::$s_IfrdCastTpnm, true, $a_ByUi);
}

function fInrUpdFld($a_This, $a_Key, $a_Val, $a_ValTpnm, $a_Vldt, $a_ByUi)
{
	// 旧值，新值，新字段，验证相关
	$l_Fld = $a_This->e_FldSet[$a_Key];
	$l_OldVal = $l_Fld->e_Val;
	$l_NewVal = $a_Val;
	$l_NewFldTpnm = $a_ValTpnm; // 【不用再次推导，使用a_ValTpnm即可】
	$l_SameTypeFail = false;
	$l_VldtRst = null;	// 表示未通过的验证器

	if ($a_Vldt) // 需要验证
	{
		// 如果新值为null或空串，而字段必填
		if (\hpnWse\fIsNullOrEstr($l_NewVal) && $l_Fld->e_Cmn->e_Optns['c_Rqrd'])
		{
			// 触发验证失败
			$l_VldtRst = nMvc\stFvMgr::$c_Rqrd;
		}
		else // 验证类型和新值
		if ($l_Fld->e_Tpnm !== $l_NewFldTpnm) // 验证类型
		{
			$l_SameTypeFail = true;
			$l_VldtRst = nMvc\stFvMgr::$c_SameType;
		}
		else // 类型相同，验证新值，包含异步验证器
		{
			$l_VldtRst = $l_Fld->eVldt(true, $a_This, $a_Key, $l_NewVal);
		}
	}

	// 如果未通过验证
	if ($l_SameTypeFail || $l_VldtRst)
	{
		// 触发验证失败
		fTrgr($l_Fld->e_Cmn->e_On['i_EgnValVldtFail'], 
			array($a_This, $a_Key, $l_NewVal, $l_VldtRst->vcGnrtInfo($a_This, $a_Key, $l_NewVal)));

		// 如果是类型错误，或不允许错误值
		if ($l_SameTypeFail || (! stNumUtil::cGetBit($l_Fld->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Err)))
		{ return false; }
	}
	else
	{
		// 触发验证成功
		fTrgr($l_Fld->e_Cmn->e_On['i_EgnValVldtSucs'], array($a_This, $a_Key, $l_NewVal, $l_OldVal));
	}

	// 通过验证，或允许错误值，更新
	$l_Fld->eAsnValAndType($l_NewVal, $l_Fld->e_Tpnm, true);

	// 触发事件，这里要通知是否由UI引起
	fTrgr($l_Fld->e_Cmn->e_On['i_EgnValUpd'], array($a_This, $a_Key, $l_NewVal, $l_OldVal, $a_ByUi));
	fTrgrUpToRoot('i_MdlUpdFld', array($a_This, $a_Key, $l_NewVal, $l_OldVal, $a_ByUi, false));
	return true;
}

function fUpdFldsToDft($a_This, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return false; }

	if ($a_This->cIsEmt()) //【PHP：必须判断这个条件】
	{ return $a_This; }

	$l_Rst = false;
	$l_Set = &$a_This->e_FldSet; $l_Key;
	foreach ($l_Set as $l_Key => $l_Fld)
	{
		$l_Rst = fUpdFldsToDft_One($a_This, $l_Fld, $l_Key, $a_ByUi) || $l_Rst;
	}
	return $l_Rst;
}

function fUpdFldsToDft_One($a_This, $a_Fld, $a_Key, $a_ByUi)
{
	if (nMvc\fIsMdl($a_Fld))
	{ return fUpdFldsToDft($a_Fld, $a_ByUi); }
	
	$l_Dft = $a_Fld->e_Cmn->e_Optns['c_Dft'];
	if ($a_Fld->e_Val === $l_Dft)
	{ return false; }
	
	fInrUpdFld($a_This, $a_Key, $l_Dft, $a_Fld->e_Tpnm, false, $a_ByUi); // 无需验证
	return true;
}

function fUpdFldsFromJson($a_This, $a_Json, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return false; }

	if ($a_This->cIsEmt()) //【PHP：必须判断这个条件】
	{ return $a_This; }

	$l_ThisIsAry = $a_This->cIsFldSetAry(); $l_JsonIsAry = \hpnWse\fIsAry($a_Json);
	if ($l_ThisIsAry != $l_JsonIsAry)
	{ return false; }

	$l_Rst = false;
	$l_Set = &$a_This->e_FldSet; $l_Len; $l_Key;
	if ($l_ThisIsAry)
	{
		$l_Len = min(count($l_Set), count($a_Json));
		for ($l_Key=0; $l_Key<$l_Len; ++$l_Key)
		{
			$l_Rst = \hpnWse\fV1OrV2(
				fUpdFldFromJson($a_This, $l_Key, stObjUtil::cFchPpty($a_Json, $l_Key), $a_ByUi), 
				$l_Rst);
		}
	}
	else
	{
		foreach ($l_Set as $l_Key => $l_Vvv)
		{
			$l_Rst = \hpnWse\fV1OrV2(
				fUpdFldFromJson($a_This, $l_Key, stObjUtil::cFchPpty($a_Json, $l_Key), $a_ByUi),
				$l_Rst);
		}
	}
	return $l_Rst;
}

function fUpdFldFromJson($a_This, $a_Key, $a_Json, $a_ByUi)
{
	// 修正a_Json
	if (\hpnWse\stObjUtil::cHasPpty($a_Json, 'Wse_Val'))
	{ $a_Json = $a_Json['Wse_Val']; }

	if (\hpnWse\fIsUdfn($a_Json))
	{ return false; }

	// 如果字段是子模型，当a_Json是纯对象或数组时递归，其余忽略
	$l_Set = &$a_This->e_FldSet; $l_Fld = $l_Set[$a_Key];
	if (nMvc\fIsMdl($l_Fld))
	{
		if (\hpnWse\fIsPureObjOrAry($a_Json))
		{ return fUpdFldsFromJson($l_Fld, $a_Json, $a_ByUi); }
	}
	else // 本征值
	{
		// 如果a_Json非原语，忽略
		if (\hpnWse\fIsAryOrObj($a_Json))
		{ return false; }

		// 不要调用公有接口，用这个内部函数
		return fUpdFld($a_This, $a_Key, $a_Json, $a_ByUi);
	}
	return false;
}

function fDltFld($a_This, $a_Key, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 删除
	$l_Fld = fAcsFld($a_This, $a_Key, true); $l_Key = stVars::$s_CnvtKey;
	if ($a_This->cIsFldSetAry())
	{
		$l_Key = stAryUtil::cIdxOf($a_This->e_FldSet, $l_Fld);
		if ($l_Key < 0)
		{ throw new \Exception('要删除的字段不在自己的字段集合里！', -1); }

		stAryUtil::cErs($a_This->e_FldSet, $l_Key);
	}
	else
	{
		unset($a_This->e_FldSet[$l_Key]);
	}

	// 如果是子模型，必须解除父子关系
	if (nMvc\fIsMdl($l_Fld))
	{ $l_Fld->e_Prn = null; }

	// // 如果已不含任何字段，清空字段集合【保持】
	// if (! $a_This->cHasAnyFld())
	// {
	// 	$a_This->e_FldSet = null;
	// }

	// 触发事件
	$l_FldVal = fFchFldVal($l_Fld);
	fTrgrUpToRoot('i_MdlDltFld', array($a_This, $l_Key, $l_FldVal, $a_ByUi, false));
	return $a_This;
}

function fDltFlds($a_This, $a_fIf, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 空集合忽略
	if ($a_This->cIsEmt())
	{ return $a_This; }

	// 内部删除
	fInrDltFlds($a_This, $a_fIf, $a_ByUi);
	return $a_This;
}

function fInrDltFlds($a_This, $a_fIf, $a_ByUi)
{
	// 先收集要删除的属性名，然后再删除
	$l_Set = &$a_This->e_FldSet;
	$l_DltFld; $l_Key; $l_Len;

	if ($a_This->cIsFldSetAry())
	{
		$l_Len = count($l_Set);
		for ($l_Key=0; $l_Key<$l_Len; ++$l_Key)
		{
			$l_DltFld = $l_Set[$l_Key];
			if ((! $a_fIf) || $a_fIf($a_This, $l_Key, fFchFldVal($l_DltFld)))
			{
				stAryUtil::cErs($l_Set, $l_Key);
				fDtchAndTrgr($a_This, $l_Key, $l_DltFld, $a_ByUi);
				-- $l_Key;
				-- $l_Len;
			}
		}
	}
	else
	{
		foreach ($l_Set as $l_Key => $l_Vvv)
		{
			$l_DltFld = $l_Set[$l_Key];
			if ((! $a_fIf) || $a_fIf($a_This, $l_Key, fFchFldVal($l_DltFld)))
			{
				unset($l_Set[$l_Key]);
				fDtchAndTrgr($a_This, $l_Key, $l_DltFld, $a_ByUi);
			}
		}
	}

	// // 如果已不含任何字段，清空字段集合【保持】
	// if (! $a_This->cHasAnyFld())
	// {
	// 	$a_This->e_FldSet = null;
	// }
}

function fDtchAndTrgr($a_This, $a_Key, $a_DltFld, $a_ByUi)
{
	// 如果是子模型，必须解除父子关系
	if (nMvc\fIsMdl($a_DltFld))
	{ $a_DltFld->e_Prn = null; }

	// 触发事件
	fTrgrUpToRoot('i_MdlDltFld', array($a_This, $a_Key, $a_DltFld, $a_ByUi, false));
}

function fClrFldSet($a_This, $a_ByUi)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 空集合忽略
	if ($a_This->cIsEmt())
	{ return $a_This; }

	// 内部删除
	fInrDltFlds($a_This, null, $a_ByUi);
	return $a_This;
}

function fSortFlds($a_This, $a_fCmpr)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 排序
	$l_FldSet = &$a_This->e_FldSet;
	if ((! $a_This->cIsFldSetAry()) || (count($l_FldSet) < 2) || (! $a_fCmpr))
	{ return; }

	fSortWithoutHide($a_This, $a_fCmpr);

	// 触发事件
	fTrgrUpToRoot('i_MdlSortFlds', array($a_This, false));
}

function fSrchFlds($a_This, $a_fSrch, $a_fCmpr)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	// 搜索
	$l_FldSet = &$a_This->e_FldSet;
	if ((! $a_This->cIsFldSetAry()) || (0 == count($l_FldSet)))
	{ return; }

	$l_IsMdl = nMvc\fIsMdl($l_FldSet[0]); //【这里假定要么都是子模型，要么都是本征值】
	$i; $l_Len = count($l_FldSet); $l_Fld; $l_Optns;
	for ($i = 0; $i<$l_Len; ++$i)
	{
		$l_Fld = $l_FldSet[$i];
		$l_Optns = &$l_Fld->e_Cmn->e_Optns;
		$l_Optns['c_Flag'] = stNumUtil::cSetBit($l_Optns['c_Flag'], utFi::i_Fi_Hide,
			! \hpnWse\fBool((! $a_fSrch) || $a_fSrch($a_This, $i, ($l_IsMdl ? $l_Fld : $l_Fld->e_Val))));
	}

	if ($a_fCmpr)
	{ fSortWithoutHide($a_This, $a_fCmpr); }

	// 触发事件
	fTrgrUpToRoot('i_MdlSrchFlds', array($a_This, false));
}

function fSortWithoutHide($a_This, $a_fCmpr)
{
	$l_Temp = $a_This->e_FldSet; //【警告】做个副本，因为回调函数里可能会调用依赖原先顺序的函数（如cGetSelfKey……）】
	$l_IsMdl = nMvc\fIsMdl($l_Temp[0]); //【这里假定要么都是子模型，要么都是本征值】
	usort($l_Temp, function ($a_F1, $a_F2) use($a_fCmpr, $l_IsMdl)
	{
		if (stNumUtil::cGetBit($a_F1->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide)) { return +1; }
		if (stNumUtil::cGetBit($a_F2->e_Cmn->e_Optns['c_Flag'], utFi::i_Fi_Hide)) { return -1; }
		return $a_fCmpr(($l_IsMdl ? $a_F1 : $a_F1->e_Val), ($l_IsMdl ? $a_F2 : $a_F2->e_Val));
	});
	$l_FldSet = &$a_This->e_FldSet; $i; // 保持住e_FldSet引用的数组
	for ($i=0; $i<count($l_Temp); ++$i)
	{ $l_FldSet[$i] = $l_Temp[$i]; }
}

function fChgEgnValFlag($a_This, $a_Key, $a_YesNo, $a_ChkCst, $a_Fi, $a_EvtName)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	$l_Fld = fAcsFld($a_This, $a_Key, true); $l_Key = stVars::$s_CnvtKey;

	// 如果是子模型，递归处理全部
	if (nMvc\fIsMdl($l_Fld))
	{
		fChgAllEgnValFlag($l_Fld, $a_YesNo, $a_ChkCst, $a_Fi, $a_EvtName);
		return $a_This;
	}

	// 本征值，或是常量，或没有变化？
	$l_Optns = &$l_Fld->e_Cmn->e_Optns;
	if (($a_ChkCst && stNumUtil::cGetBit($l_Optns['c_Flag'], utFi::i_Fi_Cst)) ||
		(stNumUtil::cGetBit($l_Optns['c_Flag'], $a_Fi) == $a_YesNo))
	{ return $a_This; }

	// 改变
	$l_Optns['c_Flag'] = stNumUtil::cSetBit($l_Optns['c_Flag'], $a_Fi, $a_YesNo);

	// 如果需要，触发事件，不冒泡
	if ($a_EvtName)
	{ fTrgr($l_Fld->e_Cmn->e_On[$a_EvtName], array($a_This, $l_Key, fFchFldVal($l_Fld), $a_YesNo)); }
	return $a_This;
}

function fChgAllEgnValFlag($a_This, $a_YesNo, $a_ChkCst, $a_Fi, $a_EvtName)
{
	// 检查锁定
	if ($a_This->cIsLock())
	{ return $a_This; }

	if ($a_This->cIsEmt()) //【PHP：必须判断这个条件】
	{ return $a_This; }

	$l_Set = &$a_This->e_FldSet; $l_Key;
	foreach ($l_Set as $l_Key => $l_Vvv)
	{
		fChgEgnValFlag($a_This, $l_Key, $a_YesNo, $a_ChkCst, $a_Fi, $a_EvtName);
	}
	return $a_This;
}

// function fFixRmtKeyFld($a_This, $a_Map)
// {
// 	// 检查锁定
// 	if ($a_This->cIsLock())
// 	{ return $a_This; }

// 	// 修正
// 	if ((! $a_This->cIsFldSetAry()) || (! $a_Map))
// 	{ return $a_This; }

// 	$l_RmtKey = $a_This->cGetRmtKeyFldKey();
// 	if (! $l_RmtKey)
// 	{ return $a_This; }

// 	$l_IsSgl = (stStrUtil::cFind($l_RmtKey, tMdl::$sc_RmtKeySprt) < 0);
// 	$l_RmtKeys = $l_IsSgl ? null : explode(tMdl::$sc_RmtKeySprt, $l_RmtKey);

// 	$l_Set = &$a_This->e_FldSet;
// 	$l_Idx; $l_Len = count($l_Set); $l_SubMdl;
// 	for ($l_Idx=0; $l_Idx<$l_Len; ++$l_Idx)
// 	{
// 		$l_SubMdl = $l_Set[$l_Idx];
// 		// if (! nMvc\fIsMdl($l_SubMdl)) //【肯定是】
// 		// { continue; }

// 		if ($l_IsSgl)
// 		{ fFixSgl($l_RmtKey, $l_SubMdl, $a_Map); }
// 		else
// 		{ fFixMlt($l_RmtKeys, $l_SubMdl, $a_Map); }
// 	}
// 	return $a_This;
// }

// function fFixSgl($a_RmtKey, $a_SubMdl, $a_Map)
// {
// 	$l_CrntVal = $a_SubMdl->cReadFld($a_RmtKey);
// 	$l_NewVal = $a_Map[$l_CrntVal];
// 	if ($l_NewVal)
// 	{
// 		$a_SubMdl->cUpdFld($a_RmtKey, $l_NewVal);
// 	}
// }

// function fFixMlt($a_RmtKeys, $a_SubMdl, $a_Map)
// {
// 	$l_CrntVal = fCmbnRmtKey($a_RmtKeys, $a_SubMdl);
// 	$l_NewVal = $a_Map[$l_CrntVal];
// 	if (! $l_NewVal)
// 	{ return; }

// 	$l_NewVal = explode(tMdl::$sc_RmtKeySprt, $l_NewVal);
// 	$l_Len = count($l_NewVal); $i;
// 	for ($i=0; $i<$l_Len; ++$i)
// 	{
// 		$a_SubMdl->cUpdFld($a_RmtKeys[$i], $l_NewVal[$i]);
// 	}
// }

//-------- 事件，验证，锁定

function fMapEvtByName($a_OfMdl, $a_EvtSys, $a_EvtName)
{
	if (! $a_EvtName)
	{ return null; }

	$l_Pn = $a_EvtName;
	if (! array_key_exists($l_Pn, $a_EvtSys))
	{ return null; }

	if (! $a_EvtSys[$l_Pn])
	{ $a_EvtSys[$l_Pn] = array(); }
	return $a_EvtSys[$l_Pn];
}

function fAddRmvMdlEvt($a_This, $a_Add, $a_EvtName, $a_fHdl, $a_IsSafe)
{
	if (! $a_fHdl) { return $a_This; }

	$l_Evt = fMapEvtByName(true, $a_This->e_Cmn->e_On, $a_EvtName);
	if (! $l_Evt)
	{ throw new \Exception(('模型不支持事件“' . $a_EvtName . '”！'), -1); }

	$a_Add ? stAryUtil::cPushIfNonExi($l_Evt, $a_fHdl) : stAryUtil::cErsIfExi($l_Evt, $a_fHdl);
	return $a_This;
}

function fAddRmvFldEvt($a_This, $a_Add, $a_Key, $a_EvtName, $a_fHdl, $a_IsSafe)
{
	if (! $a_fHdl) { return $a_This; }

	$l_Fld = fAcsFld($a_This, $a_Key, $a_Add); $l_Key = stVars::$s_CnvtKey; // 添加时检查，移除时不用
	if (nMvc\fIsMdl($l_Fld))
	{
		fAddRmvMdlEvt($l_Fld, $a_Add, $a_EvtName, $a_fHdl, $a_IsSafe);
		return $a_This;
	}

	$l_Evt = fMapEvtByName(false, $l_Fld->e_Cmn->e_On, $a_EvtName);
	if (! $l_Evt)
	{ throw new \Exception(('字段不支持事件“' . $a_EvtName . '”！'), -1); }

	$a_Add ? stAryUtil::cPushIfNonExi($l_Evt, $a_fHdl) : stAryUtil::cErsIfExi($l_Evt, $a_fHdl);
	return $a_This;
}

function fClrMdlEvtHdlr($a_This)
{
	$a_This->e_Cmn->eInitOn(true);
	return $a_This;
}

function fClrFldEvtHdlr($a_This, $a_Key)
{
	$l_Fld = fAcsFld($a_This, $a_Key, true); $l_Key = stVars::$s_CnvtKey;
	$l_Fld->e_Cmn->eInitOn(false);
	return $a_This;
}

function fTrgr($a_Evt, $a_Agms) // a_Agms[0]一定是tMdl，【接收副本】
{
	if (! $a_Evt)
	{ return; }

	// 入队
	$l_Que = &$a_Agms[0]->cAcsRoot()->e_TrgrQue->c_Que;
	$l_Que[] = function () use($a_Evt)
	{
		stAryUtil::cFor($a_Evt, function ($a_A, $a_I, $a_fHdl) use($a_Agms)
		{
			stFctnUtil::cApl(null, $a_fHdl, $a_Agms);
		});
	};
}

function fTrgrUpToRoot($a_Which, $a_Agms) // a_Agms[0]一定是tMdl
{
	fTrgr($a_Agms[0]->e_Cmn->e_On[$a_Which], $a_Agms); //【a_Agms拷贝一份！】

	// 冒泡
	$a_Agms[count($a_Agms) - 1] = true; // a_IsBbl
	$l_Prn = $a_Agms[0]->e_Prn;
	while ($l_Prn)
	{
		fTrgr($l_Prn->e_Cmn->e_On[$a_Which], $a_Agms);
		$l_Prn = $l_Prn->e_Prn;
	}
}

function fTrgrQue($a_This)
{
	// 第一个条件保证只有“始发调用”才能执行后面的代码，
	// 调用队列里的函数总是作为始发调用的尾调用！
	$l_Root = $a_This->cAcsRoot();
	$l_TrgrQue = $l_Root->e_TrgrQue;
	$l_Que = &$l_TrgrQue->c_Que;
	if ($l_TrgrQue->c_Trvsn || (0 == count($l_Que)))
	{ return $a_This; }

	try
	{
		$l_TrgrQue->c_Trvsn = true;		// 开始遍历
		while (count($l_Que) > 0)		// 按序执行，【注意，在调用期间可能会继续增长！】
		{
			$l_fHdl = array_shift($l_Que);
			$l_fHdl();
		}
	}
	catch (\Exception $a_Exc) //【旧版PHP不支持finally，模拟之】
	{
		$l_Exc = $a_Exc;
	}
	// finally
	// {
		stAryUtil::cZero($l_Que);       // 清空
		$l_TrgrQue->c_Trvsn = false;	// 结束遍历
	// }

	if (isset($l_Exc))
	{
		throw $l_Exc;
	}
	return $a_This;
}

function fVldtFor($a_Ary, $a_Len, $a_Thro, &$a_ErrAry, $a_Mdl, $a_Key, $a_NewVal)
{
	$l_Rst = true;
	$i;
	for ($i=0; $i<$a_Len; ++$i)
	{
		if ((! $a_Ary[$i]->c_Dsab) && (! $a_Ary[$i]->vcRun($a_Mdl, $a_Key, $a_NewVal)))
		{
			if ($a_Thro)
			{
				$l_Rst = false;
				if ($a_ErrAry) //【PHP：没办法，总是成立，因为引用不能绑定null】
				{ array_push($a_ErrAry, $a_Ary[$i]->vcGnrtInfo($a_Mdl, $a_Key, $a_NewVal)); }
			}
			else
			{ return $a_Ary[$i]; }
		}
	}
	return $a_Thro ? $l_Rst : null;
}

function fVldtFld($a_This, $a_Key)
{
	$l_Fld = fAcsFld($a_This, $a_Key, true); $l_Key = stVars::$s_CnvtKey;
	$l_ErrInfo = array();
	return nMvc\fIsMdl($l_Fld) //【PHP：因为引用不能绑定null，所以传一个空数组吧！】
			? fVldtMdl($l_Fld, $l_ErrInfo) 
			: fVldtEgnVal($a_This, $l_Key, $l_Fld, $l_ErrInfo);
}

function fVldtEgnVal($a_This, $a_Key, $a_Fld, &$a_ErrInfo)
{
	// 先检查必填字段是否为空，再检查其他，排除异步验证器
	$l_FldVal = $a_Fld->e_Val;
	$l_VldtRst = null;
	$l_GnrtInfo;
	if (\hpnWse\fIsNullOrEstr($l_FldVal) && $a_Fld->e_Cmn->e_Optns['c_Rqrd'])
	{ $l_VldtRst = nMvc\stFvMgr::$c_Rqrd; }
	else
	{ $l_VldtRst = $a_Fld->eVldt(false, $a_This, $a_Key, $l_FldVal); }

	if (! $l_VldtRst)
	{
		fTrgr($a_Fld->e_Cmn->e_On['i_EgnValVldtSucs'], 
			array($a_This, $a_Key, $l_FldVal, $l_FldVal));
	}
	else
	{
		$l_GnrtInfo = $l_VldtRst->vcGnrtInfo($a_This, $a_Key, $l_FldVal);
		fTrgr($a_Fld->e_Cmn->e_On['i_EgnValVldtFail'], 
			array($a_This, $a_Key, $l_FldVal, $l_GnrtInfo));
		if (isset($a_ErrInfo['c_Flds']) && (! isset($a_ErrInfo['c_Flds'][$a_Key]))) // 只记录第一条
		{
			$a_ErrInfo['c_Flds'][$a_Key] = $l_GnrtInfo;
		}
	}
	return (null == $l_VldtRst);
}

function fVldtMdl($a_This, &$a_ErrInfo, $a_MdlOnly = false)
{
	// 空模型验证一定通过
	if ($a_This->cIsEmt())
	{ return true; }

	// 如果需要，验证每个字段
	$l_Rst = true;
	$l_Set = &$a_This->e_FldSet;
	$l_Key; $l_Fld; $l_Flag; $l_VldtRst;
	if (! \hpnWse\fBool($a_MdlOnly))
	{
		foreach ($l_Set as $l_Key => $l_Vvv)
		{
			// 跳过常量和隐藏
			$l_Fld = $l_Set[$l_Key];
			$l_Flag = $l_Fld->e_Cmn->e_Optns['c_Flag'];
			if (stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Cst) || 
				stNumUtil::cGetBit($l_Flag, utFi::i_Fi_Hide))
			{ continue; }

			// 如果是模型，递归，注意保持l_Rst
			if (nMvc\fIsMdl($l_Fld))
			{ $l_Rst = fVldtMdl($l_Fld, $a_ErrInfo) && $l_Rst; }
			else // 本征值
			{ $l_Rst = fVldtEgnVal($a_This, $l_Key, $l_Fld, $a_ErrInfo) && $l_Rst; }
		}
	}

	// 验证自身，这是穿越型，返回Boolean，注意保持l_Rst，并把错误消息录入汇总数组
	$l_Vldtrs = &$a_This->e_Cmn->e_Vldtrs;
	$l_ErrAry = array();	if (stObjUtil::cHasPpty($a_ErrInfo, 'c_Smry')) { $l_ErrAry = &$a_ErrInfo['c_Smry']; } //【PHP：只能这么写了！】
	$l_Rst = fVldtFor($l_Vldtrs, count($l_Vldtrs), true, $l_ErrAry, $a_This, null, \Wse::$i_Udfn) && $l_Rst;
	if (! $l_Rst)
	{ fTrgr($a_This->e_Cmn->e_On['i_MdlVldtFail'], array($a_This, $a_ErrInfo)); }
	else
	{ fTrgr($a_This->e_Cmn->e_On['i_MdlVldtSucs'], array($a_This, null)); }
	return $l_Rst;
}

function fAcsFvByTpnm(&$a_FvAry, $a_Tpnm)
{
	$l_Idx = stAryUtil::cFind($a_FvAry, 
		function ($a_Ary, $a_Idx, $a_Fv) use($a_Tpnm) { return stObjUtil::cGetTpnm($a_Fv) == $a_Tpnm; });
	return ($l_Idx >= 0) ? $a_FvAry[$l_Idx] : null;
}

function fLockUlok($a_This, $a_Lock)
{
	// 首先处理自身
	$l_Trgr;
	if ($a_Lock)
	{
		$l_Trgr = (0 == $a_This->e_LockCnt);
		++ $a_This->e_LockCnt;
	}
	else
	if ($a_This->e_LockCnt > 0)
	{
		-- $a_This->e_LockCnt;
		$l_Trgr = (0 == $a_This->e_LockCnt);
	}

	if ($l_Trgr) // 触发事件
	{ fTrgrUpToRoot('i_MdlLockChgd', array($a_This, false)); }

	// 然后处理子树
	fLockUlokSbtr($a_This, $a_Lock);
}

function fLockUlokSbtr($a_This, $a_Lock)
{
	$l_Set = &$a_This->e_FldSet;
	$l_Key; $l_Fld;
	foreach ($l_Set as $l_Key => $l_Vvv)
	{
		// 如果是模型，递归
		$l_Fld = $l_Set[$l_Key];
		if (nMvc\fIsMdl($l_Fld))
		{ fLockUlok($l_Fld, $a_Lock); }
	}
}

//-------- 拷贝、比较

function fCopy($a_Orig, $a_Srlz)
{
	$l_Copy = new tMdl();
	if ($a_Orig->cIsEmt()) // 空模型
	{ return $l_Copy; }

	// 利用序列化
	$l_Json = $a_Orig->cToJson(null, $a_Srlz);
	fMapFromJson($l_Copy, $l_Json, $a_Srlz);
	return $l_Copy;
}

// function fFldsCmpr_BaseChk($a_L, $a_R)
// {
// 	// 都是空模型时相等，字段集类型或字段数量不同时不相等
// 	if ($a_L->cIsEmt() && $a_R->cIsEmt())
// 	{ return true; }
// 	if (($a_L->cIsFldSetObj() != $a_R->cIsFldSetObj()) || ($a_L->cGetFldAmt() != $a_R->cGetFldAmt()))
// 	{ return false; }

// 	// 未知
// 	return null;
// }

// function fFldsEq($a_L, $a_R)
// {
// 	$l_BaseChk = fFldsCmpr_BaseChk($a_L, $a_R);
// 	if (null !== $l_BaseChk)
// 	{ return $l_BaseChk; }

// 	// 相互包含？
// 	return fFldsEq_Ctan($a_L, $a_R);// && fFldsEq_Ctan($a_R, $a_L); // 由于字段一样多，不用相互
// }

// function fFldsSampType($a_L, $a_R)
// {
// 	$l_BaseChk = fFldsCmpr_BaseChk($a_L, $a_R);
// 	if (null !== $l_BaseChk)
// 	{ return $l_BaseChk; }

// 	// 相互包含？只要求类型
// 	return fFldsEq_Ctan($a_L, $a_R, true);// && fFldsEq_Ctan($a_R, $a_L, true); // 由于字段一样多，不用相互
// }

// function fFldsEq_Ctan($a_L, $a_R, $a_TypeOnly) // L 包含 R ？
// {
// 	$l_SetL = &$a_L->e_FldSet; $l_SetR = &$a_R->e_FldSet;
// 	$l_Key; $l_FldL; $l_FldR;
// 	foreach ($l_SetR as $l_Key => $l_Vvv) // 对 R 中每个键
// 	{
// 		if (! array_key_exists($l_Key, $l_SetL)) // 若不在 L 中
// 		{ return false; }

// 		// 比较字段，要求类型与值都相等；对于子模型，间接递归
// 		$l_FldL = $l_SetL[$l_Key];
// 		$l_FldR = $l_SetR[$l_Key];
// 		if ($l_FldL->e_Tpnm !== $l_FldR->e_Tpnm)
// 		{ return false; }

// 		if ($l_FldL->e_Tpnm === 'tMdl')
// 		{
// 			if ($a_TypeOnly)
// 			{
// 				if (! fFldsSampType($l_FldL, $l_FldR))
// 				{ return false; }
// 			}
// 			else
// 			{
// 				if (! fFldsEq($l_FldL, $l_FldR))
// 				{ return false; }
// 			}
// 		}

// 		if ($a_TypeOnly) // 只要求类型相等
// 		{ continue; }

// 		if ($l_FldL->e_Val !== $l_FldR->e_Val)
// 		{ return false; }
// 	}
// 	return true;
// }

} // namespace hpnWse\nMvc\unMdl
//////////////////////////////////// OVER ////////////////////////////////////